diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 1fd898684cf1c1..700dadbb09c815 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -1720,42 +1720,62 @@ BOOL STDMETHODCALLTYPE EEDllMain( // TRUE on success, FALSE on error. struct TlsDestructionMonitor { - bool m_activated = false; + SIZE_T m_activatedOrThreadId = 0; - void Activate() + void Activate(Thread *thread) { - m_activated = true; +#ifdef TARGET_UNIX + // The thread ID is checked in the destructor + m_activatedOrThreadId = thread->GetOSThreadId64(); +#else // !TARGET_UNIX + m_activatedOrThreadId = 1; +#endif // TARGET_UNIX + } + + void Deactivate() + { + m_activatedOrThreadId = 0; } ~TlsDestructionMonitor() { - if (m_activated) + if (m_activatedOrThreadId == 0) + { + return; + } + +#ifdef TARGET_UNIX + // Some Linux toolset versions call thread-local destructors during shutdown on a wrong thread. + if (PAL_GetCurrentOSThreadId() != m_activatedOrThreadId) + { + return; + } +#endif // TARGET_UNIX + + Thread* thread = GetThreadNULLOk(); + if (thread) { - Thread* thread = GetThreadNULLOk(); - if (thread) - { #ifdef FEATURE_COMINTEROP - // reset the CoInitialize state - // so we don't call CoUninitialize during thread detach - thread->ResetCoInitialized(); + // reset the CoInitialize state + // so we don't call CoUninitialize during thread detach + thread->ResetCoInitialized(); #endif // FEATURE_COMINTEROP - // For case where thread calls ExitThread directly, we need to reset the - // frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode. - // We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode - if (thread->m_pFrame != FRAME_TOP) - { + // For case where thread calls ExitThread directly, we need to reset the + // frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode. + // We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode + if (thread->m_pFrame != FRAME_TOP) + { #ifdef _DEBUG - thread->m_GCOnTransitionsOK = FALSE; + thread->m_GCOnTransitionsOK = FALSE; #endif - GCX_COOP_NO_DTOR(); - thread->m_pFrame = FRAME_TOP; - GCX_COOP_NO_DTOR_END(); - } - thread->DetachThread(TRUE); + GCX_COOP_NO_DTOR(); + thread->m_pFrame = FRAME_TOP; + GCX_COOP_NO_DTOR_END(); } - - ThreadDetaching(); + thread->DetachThread(TRUE); } + + ThreadDetaching(); } }; @@ -1763,9 +1783,14 @@ struct TlsDestructionMonitor // is called when a thread is being shut down. thread_local TlsDestructionMonitor tls_destructionMonitor; -void EnsureTlsDestructionMonitor() +void EnsureTlsDestructionMonitor(Thread *thread) +{ + tls_destructionMonitor.Activate(thread); +} + +void DeactivateTlsDestructionMonitor() { - tls_destructionMonitor.Activate(); + tls_destructionMonitor.Deactivate(); } #ifdef DEBUGGING_SUPPORTED diff --git a/src/coreclr/vm/ceemain.h b/src/coreclr/vm/ceemain.h index 1404a5a04237ff..3efc8b1cd169b2 100644 --- a/src/coreclr/vm/ceemain.h +++ b/src/coreclr/vm/ceemain.h @@ -17,6 +17,7 @@ #include // for HFILE, HANDLE, HMODULE class EEDbgInterfaceImpl; +class Thread; // Ensure the EE is started up. HRESULT EnsureEEStarted(); @@ -45,7 +46,8 @@ void ForceEEShutdown(ShutdownCompleteAction sca = SCA_ExitProcessWhenShutdownCom // Notification of a DLL_THREAD_DETACH or a Thread Terminate. void ThreadDetaching(); -void EnsureTlsDestructionMonitor(); +void EnsureTlsDestructionMonitor(Thread *thread); +void DeactivateTlsDestructionMonitor(); void SetLatchedExitCode (INT32 code); INT32 GetLatchedExitCode (void); diff --git a/src/coreclr/vm/eepolicy.cpp b/src/coreclr/vm/eepolicy.cpp index af27338544e861..7dc22a2eedef4c 100644 --- a/src/coreclr/vm/eepolicy.cpp +++ b/src/coreclr/vm/eepolicy.cpp @@ -78,6 +78,13 @@ void SafeExitProcess(UINT exitCode, ShutdownCompleteAction sca = SCA_ExitProcess // disabled because if we fault in this code path we will trigger our Watson code CONTRACT_VIOLATION(ThrowsViolation); + // The TlsDestructionMonitor for this thread would likely be destructed at some point after ExitProcess is called. On + // Windows, this happens after all other threads in the process are torn down, and may occur while a GC is in progress. + // The thread cleanup code in TlsDestructionMonitor may try to enter cooperative GC mode to fix the frame pointer and + // wait for the pending GC to complete, leading to a hang. Since the process is being exited, deactivate the + // TlsDestructionMonitor for this thread before calling ExitProcess. + DeactivateTlsDestructionMonitor(); + ExitProcess(exitCode); } } diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index f03bd013c0f99c..71b434601851e0 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -369,7 +369,7 @@ void SetThread(Thread* t) gCurrentThreadInfo.m_pThread = t; if (t != NULL) { - EnsureTlsDestructionMonitor(); + EnsureTlsDestructionMonitor(t); } } diff --git a/src/tests/issues.targets b/src/tests/issues.targets index e142ff1292411b..7d95fa9bc4a750 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -30,6 +30,9 @@ This test is to verify we are running mono, and therefore only makes sense on mono. + + https://github.com/dotnet/runtime/issues/83658 + https://github.com/dotnet/runtime/issues/5933 @@ -251,9 +254,6 @@ - - https://github.com/dotnet/runtime/issues/84006 -