Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Free TLS pointers for every thread

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?

like image 516
macro_controller Avatar asked Aug 16 '17 08:08

macro_controller


1 Answers

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 the DLL_THREAD_DETACH value for the individual threads of the process. The DLL is only sent a DLL_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, FlsFreeFlsGetValue, and FlsSetValue) 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.

like image 168
Remy Lebeau Avatar answered Nov 18 '22 00:11

Remy Lebeau