Merge pull request #1504 from Lectem/windows-dll-unload-hang

Don't deadlock when joining threads from a DLL being unloaded.
This commit is contained in:
Bartosz Taudul
2026-10-29 19:20:29 +01:00
committed by GitHub
+36 -2
View File
@@ -42,22 +42,56 @@ public:
Thread( void(*func)( void* ptr ), void* ptr )
: m_func( func )
, m_ptr( ptr )
, m_exit( CreateEventW( nullptr, TRUE, FALSE, nullptr ) )
, m_hnd( CreateThread( nullptr, 0, Launch, this, 0, nullptr ) )
{}
~Thread()
{
WaitForSingleObject( m_hnd, INFINITE );
// The thread signals m_exit when it is done and then parks, instead of exiting, so that
// it can be joined from DllMain (e.g. a static Profiler destroyed by FreeLibrary).
// A normal thread exit needs the loader lock, which the joining thread already holds.
HANDLE handles[2] = { m_exit, m_hnd };
WaitForMultipleObjects( 2, handles, FALSE, INFINITE );
CloseHandle( m_exit );
if( InLoaderCallout() )
{
TerminateThread( m_hnd, 0 );
}
else
{
QueueUserAPC( WakeUp, m_hnd, 0 );
WaitForSingleObject( m_hnd, INFINITE );
}
CloseHandle( m_hnd );
}
HANDLE Handle() const { return m_hnd; }
private:
static DWORD WINAPI Launch( void* ptr ) { ((Thread*)ptr)->m_func( ((Thread*)ptr)->m_ptr ); return 0; }
static bool InLoaderCallout()
{
typedef BOOLEAN(NTAPI* IsWithinCallout)();
static const auto fn = (IsWithinCallout)GetProcAddress( GetModuleHandleW( L"ntdll.dll" ), "RtlIsThreadWithinLoaderCallout" );
return fn && fn();
}
static void CALLBACK WakeUp( ULONG_PTR ) {}
static DWORD WINAPI Launch( void* ptr )
{
auto self = (Thread*)ptr;
self->m_func( self->m_ptr );
// *self may be gone as soon as m_exit is signaled.
const HANDLE exit = self->m_exit;
SetEvent( exit );
SleepEx( INFINITE, TRUE );
return 0;
}
void(*m_func)( void* ptr );
void* m_ptr;
HANDLE m_exit;
HANDLE m_hnd;
};