Reading the following page in MSDN:
Using Thread Local Storage in a Dynamic-Link Library
I can't understand who is responsible for freeing the memory pointed by the TLS slot of each thread in the case of a FreeLibrary()
call.
From what I understand, if I have several running threads, all of them allocated memory inside a TLS slot in a given index. When FreeLibrary()
is called, only DLL_PROCESS_DETACH
is triggered, so only the thread which receives the DLL_PROCESS_DETACH
notification has a chance to call LocalFree()
on its own data stored in the TLS slot, before calling TlsFree()
on the index. This causes a memory leak in all of the other threads that don't have a chance to call LocalFree()
on their data, as they don't receive a DLL_THREAD_DETACH
notification.
Can someone explain when and where the buffers stored in TLS slots of each thread should be freed?
Per the documentation you linked to:
When a thread terminates, the entry-point function is called with the DLL_THREAD_DETACH value and the memory for that thread is freed.
That is the ideal time when the memory pointed to by the TLS slot should be freed, if the thread did not already do so before terminating. This is demonstrated in the example code provided:
case DLL_THREAD_DETACH:
// Release the allocated memory for this thread.
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData);
break;
case DLL_PROCESS_DETACH:
// Release the allocated memory for this thread.
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData);
// Release the TLS index.
TlsFree(dwTlsIndex);
break;
However, per the DllMain entry point documentation:
When a DLL is unloaded from a process as a result of an unsuccessful load of the DLL, termination of the process, or a call to
FreeLibrary
, the system does not call the DLL's entry-point function with theDLL_THREAD_DETACH
value for the individual threads of the process. The DLL is only sent aDLL_PROCESS_DETACH
notification. DLLs can take this opportunity to clean up all resources for all threads known to the DLL.
So, you would have to keep track of the pointers you store in per-thread TLS slots in such a way that any pointers not already freed by the DLL_THREAD_DETACH
handler can be later freed by the DLL_PROCESS_DETACH
handler. For instance, by storing the pointers in a global thread-safe list.
Update: an alternative solution would be to have the DLL_PROCESS_DETACH
handler enumerate running threads, accessing the TIB/TEB (Thread Information/Environment Block) struct for each thread. NtQueryInformationThread()
can be used to retrieve a pointer to a thread's TIB/TEB. Amongst other things, the TIB/TEB also contains a pointer to the thread's TLS array.
Update: On Vista+, an alternative solution is to use FLS (fiber local storage) instead of TLS (thread local storage). The FlsAlloc()
function takes an optional callback. The FlsCallback
documentation states:
An application-defined function. If the FLS slot is in use, FlsCallback is called on fiber deletion, thread exit, and when an FLS index is freed.
And the FlsFree()
documentation states:
Freeing an FLS index frees the index for all instances of FLS in the current process. Freeing an FLS index also causes the associated callback routine to be called for each fiber, if the corresponding FLS slot contains a non-NULL value.
Per the Fibers documentation:
A fiber can use fiber local storage (FLS) to create a unique copy of a variable for each fiber. If no fiber switching occurs, FLS acts exactly the same as thread local storage. The FLS functions (
FlsAlloc
,FlsFree
,FlsGetValue
, andFlsSetValue
) manipulate the FLS associated with the current thread. If the thread is executing a fiber and the fiber is switched, the FLS is also switched.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With