From 5044e936ce154fcf3223cbe6f5e2ad66605cea9f Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Mon, 1 Jul 2024 17:13:07 -0700 Subject: [PATCH] Use RtlDllShutdownInProgress to detect process shutdown on Windows (#103877) * Use RtlDllShutdownInProgress to detect process shutdown on Windows Switching to cooperative mode is not safe during process shutdown on Windows. Process shutdown can terminate a thread in the middle of the GC. The shutdown thread deadlocks if it tries to switch to cooperative mode and wait for the GC to finish in this situation. Use RtlDllShutdownInProgress Windows API to detect process shutdown to skip cleanup that has to be done in cooperative mode. The existing g_fProcessDetach flag is set too late - using this flag to skip cooperative mode switch would lead to shutdown deadlocks, and the existing g_fEEShutDown flag is set too early - using this flag to skip cooperative mode switch would lead to shutdown crashes. Fixes #103624 * Replace direct g_fProcessDetach checks with a call to IsAtProcessExit --- src/coreclr/debug/ee/controller.cpp | 2 +- src/coreclr/debug/ee/debugger.cpp | 32 ++-- src/coreclr/debug/ee/debugger.h | 2 +- src/coreclr/debug/ee/debuggermodule.cpp | 4 +- src/coreclr/debug/ee/frameinfo.cpp | 2 +- src/coreclr/debug/ee/rcthread.cpp | 12 +- src/coreclr/vm/cachelinealloc.cpp | 2 +- src/coreclr/vm/ceemain.cpp | 54 +++---- src/coreclr/vm/codeman.cpp | 4 +- src/coreclr/vm/comcache.cpp | 14 +- src/coreclr/vm/comcallablewrapper.cpp | 2 +- src/coreclr/vm/crst.cpp | 4 +- src/coreclr/vm/crst.h | 4 - .../vm/eventing/eventpipe/ep-rt-coreclr.h | 2 +- src/coreclr/vm/interoputil.cpp | 4 +- src/coreclr/vm/runtimecallablewrapper.cpp | 2 +- src/coreclr/vm/stdinterfaces.cpp | 4 +- src/coreclr/vm/stringliteralmap.cpp | 2 +- src/coreclr/vm/syncclean.cpp | 2 +- src/coreclr/vm/threads.cpp | 151 +++++++----------- src/coreclr/vm/threads.h | 8 +- src/coreclr/vm/threadstatics.cpp | 74 ++++----- src/coreclr/vm/threadstatics.h | 4 +- src/coreclr/vm/threadsuspend.cpp | 2 +- src/coreclr/vm/vars.hpp | 14 +- 25 files changed, 168 insertions(+), 239 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index a285a98b183de..46a4f74f85cf2 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -1109,7 +1109,7 @@ void DebuggerController::DisableAll() // thus leaving the patchtable in an inconsistent state such that we may fail trying to walk it. // Since we're exiting anyways, leaving int3 in the code can't harm anybody. // - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { HASHFIND f; for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f); diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index 32c4fba244f00..e3828e53029b1 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -328,7 +328,7 @@ void Debugger::DoNotCallDirectlyPrivateLock(void) // Lock becomes no-op in late shutdown. - if (g_fProcessDetach) + if (IsAtProcessExit()) { return; } @@ -430,7 +430,7 @@ void Debugger::DoNotCallDirectlyPrivateUnlock(void) // Controller lock is "smaller" than debugger lock. - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { #ifdef _DEBUG if (m_mutexCount == 1) @@ -5035,7 +5035,7 @@ void Debugger::SendSyncCompleteIPCEvent(bool isEESuspendedForGC) PRECONDITION(ThreadHoldsLock()); // Anyone sending the synccomplete must hold the TSL. - PRECONDITION(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + PRECONDITION(ThreadStore::HoldingThreadStore() || IsAtProcessExit()); // The sync complete is now only sent on a helper thread. if (!isEESuspendedForGC) @@ -5069,7 +5069,7 @@ void Debugger::SendSyncCompleteIPCEvent(bool isEESuspendedForGC) // We know we're not on the shutdown thread here. // And we also know we can't block the shutdown thread (b/c it has the TSL and will // get a free pass through the GC toggles that normally block threads for debugging). - if (g_fProcessDetach) + if (IsAtProcessExit()) { STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SSCIPCE: Skipping for shutdown.\n"); return; @@ -5304,7 +5304,7 @@ void Debugger::TrapAllRuntimeThreads() // If we're doing shutdown, then don't bother trying to communicate w/ the RS. // If we're not the thread doing shutdown, then we may be asynchronously killed by the OS. // If we are the thread in shutdown, don't TART b/c that may block and do complicated stuff. - if (g_fProcessDetach) + if (IsAtProcessExit()) { STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::TART: Skipping for shutdown.\n"); return; @@ -5355,7 +5355,7 @@ void Debugger::TrapAllRuntimeThreads() // That means that our helper is not blocked on starting up, thus we can wait infinite on it. // Thus we don't need to do helper duty if the suspend fails. bool fShouldDoHelperDuty = !m_pRCThread->IsRCThreadReady() && fSuspended; - if (fShouldDoHelperDuty && !g_fProcessDetach) + if (fShouldDoHelperDuty && !IsAtProcessExit()) { // In V1.0, we had the assumption that if the helper thread isn't ready yet, then we're in // a state that SuspendForDebug will succeed on the first try, and thus we'll @@ -9139,7 +9139,7 @@ BOOL Debugger::SuspendComplete(bool isEESuspendedForGC) // If happen on managed thread, it must be doing the helper thread duty. // - _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + _ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit()); // We should be holding debugger lock m_mutex. _ASSERTE(ThreadHoldsLock()); @@ -10739,7 +10739,7 @@ bool Debugger::HandleIPCEvent(DebuggerIPCEvent * pEvent) (pThread != NULL) ? GetThreadIdHelper(pThread) : 0, debugState)); - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { g_pEEInterface->SetAllDebugState(pThread, debugState); } @@ -10868,7 +10868,7 @@ bool Debugger::HandleIPCEvent(DebuggerIPCEvent * pEvent) { pIPCResult->hr = CORDBG_E_NOTREADY; } - else if (!g_fProcessDetach) + else if (!IsAtProcessExit()) { // // Since this pointer is coming from the RS, it may be NULL or something @@ -15056,7 +15056,7 @@ HRESULT Debugger::FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo, if (pThread->m_State & Thread::TS_AbortRequested) return CORDBG_E_FUNC_EVAL_BAD_START_POINT; - if (g_fProcessDetach) + if (IsAtProcessExit()) return CORDBG_E_FUNC_EVAL_BAD_START_POINT; // If there is no guard page on this thread, then we've taken a stack overflow exception and can't run managed @@ -15251,7 +15251,7 @@ Debugger::FuncEvalAbort( "D::FEA: performing UserAbort on thread %#x, id=0x%x\n", pDE->m_thread, GetThreadIdHelper(pDE->m_thread))); - if (!g_fProcessDetach && !pDE->m_completed) + if (!IsAtProcessExit() && !pDE->m_completed) { // // Perform a stop on the thread that the eval is running on. @@ -15317,7 +15317,7 @@ Debugger::FuncEvalRudeAbort( "D::FEA: performing RudeAbort on thread %#x, id=0x%x\n", pDE->m_thread, Debugger::GetThreadIdHelper(pDE->m_thread))); - if (!g_fProcessDetach && !pDE->m_completed) + if (!IsAtProcessExit() && !pDE->m_completed) { // // Perform a stop on the thread that the eval is running on. @@ -15608,7 +15608,7 @@ void Debugger::DisableDebugger(void) * This is called in the case that the loader lock is held and so no new * threads can be spun up to be the helper thread, so the existing thread * must be the helper thread until a new one can spin up. - * This is also called in the shutdown case (g_fProcessDetach==true) and our + * This is also called in the shutdown case (IsAtProcessExit()==true) and our * helper may have already been blown away. ***************************************************************************/ void Debugger::DoHelperThreadDuty() @@ -15628,7 +15628,7 @@ void Debugger::DoHelperThreadDuty() // We'll get killed randomly anyways, so not much we can do. // These assumptions are based off us being called from TART. - _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); // got this from TART + _ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit()); // got this from TART _ASSERTE(m_trappingRuntimeThreads); // We're only called from TART. _ASSERTE(!m_stopped); // we haven't sent the sync-complete yet. @@ -16179,7 +16179,7 @@ void Debugger::AcquireDebuggerDataLock(Debugger *pDebugger) { WRAPPER_NO_CONTRACT; - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { pDebugger->GetDebuggerDataLock()->Enter(); } @@ -16190,7 +16190,7 @@ void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger) { WRAPPER_NO_CONTRACT; - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { pDebugger->GetDebuggerDataLock()->Leave(); } diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index fa538f0aed954..48f0e23e6228c 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -2492,7 +2492,7 @@ class Debugger : public DebugInterface } CONTRACTL_END; - if (g_fProcessDetach) + if (IsAtProcessExit()) return true; if (g_pEEInterface->GetThread()) diff --git a/src/coreclr/debug/ee/debuggermodule.cpp b/src/coreclr/debug/ee/debuggermodule.cpp index 633567f0dd85f..06b7c7b7f149e 100644 --- a/src/coreclr/debug/ee/debuggermodule.cpp +++ b/src/coreclr/debug/ee/debuggermodule.cpp @@ -113,8 +113,8 @@ DebuggerModuleTable::~DebuggerModuleTable() #ifdef _DEBUG bool DebuggerModuleTable::ThreadHoldsLock() { - // In shutdown (g_fProcessDetach), the shutdown thread implicitly holds all locks. - return g_fProcessDetach || g_pDebugger->HasDebuggerDataLock(); + // In shutdown (IsAtProcessExit()), the shutdown thread implicitly holds all locks. + return IsAtProcessExit() || g_pDebugger->HasDebuggerDataLock(); } #endif diff --git a/src/coreclr/debug/ee/frameinfo.cpp b/src/coreclr/debug/ee/frameinfo.cpp index e69c50c4d0b8c..48e0d2d56c146 100644 --- a/src/coreclr/debug/ee/frameinfo.cpp +++ b/src/coreclr/debug/ee/frameinfo.cpp @@ -1875,7 +1875,7 @@ bool ShouldSendUMLeafChain(Thread * pThread) // If we're in shutodown, don't bother trying to sniff for an UM leaf chain. // @todo - we'd like to never even be trying to stack trace on shutdown, this // comes up when we do helper thread duty on shutdown. - if (g_fProcessDetach) + if (IsAtProcessExit()) { return false; } diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index 4d4c60e82a687..2ebc40130b4b3 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -858,7 +858,7 @@ void DebuggerRCThread::MainLoop() PRECONDITION(m_thread != NULL); PRECONDITION(ThisIsHelperThreadWorker()); PRECONDITION(IsDbgHelperSpecialThread()); // Can only be called on native debugger helper thread - PRECONDITION((!ThreadStore::HoldingThreadStore()) || g_fProcessDetach); + PRECONDITION((!ThreadStore::HoldingThreadStore()) || IsAtProcessExit()); } CONTRACTL_END; @@ -960,7 +960,7 @@ void DebuggerRCThread::MainLoop() { // If they called continue, then we must have released the TSL. - _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach); + _ASSERTE(!ThreadStore::HoldingThreadStore() || IsAtProcessExit()); // Let's release the lock here since runtime is resumed. debugLockHolderSuspended.Release(); @@ -1062,7 +1062,7 @@ void DebuggerRCThread::MainLoop() // We also hold debugger lock the whole time that Runtime is stopped. We will release the debugger lock // when we receive the Continue event that resumes the runtime. - _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + _ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit()); } else { @@ -1107,7 +1107,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() // It should be holding the debugger lock!!! // PRECONDITION(m_debugger->ThreadHoldsLock()); - PRECONDITION((ThreadStore::HoldingThreadStore()) || g_fProcessDetach); + PRECONDITION((ThreadStore::HoldingThreadStore()) || IsAtProcessExit()); PRECONDITION(ThisIsTempHelperThread()); } CONTRACTL_END; @@ -1177,7 +1177,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() if (fWasContinue) { // If they called continue, then we must have released the TSL. - _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach); + _ASSERTE(!ThreadStore::HoldingThreadStore() || IsAtProcessExit()); #ifdef _DEBUG // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible @@ -1241,7 +1241,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() dwWaitTimeout = INFINITE; // Note: we hold the thread store lock now and debugger lock... - _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + _ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit()); } } diff --git a/src/coreclr/vm/cachelinealloc.cpp b/src/coreclr/vm/cachelinealloc.cpp index 8ea226ae93764..4c32903c6de39 100644 --- a/src/coreclr/vm/cachelinealloc.cpp +++ b/src/coreclr/vm/cachelinealloc.cpp @@ -67,7 +67,7 @@ CCacheLineAllocator::~CCacheLineAllocator() { if(tempPtr->m_pAddr[i] != NULL) { - if (!g_fProcessDetach) + if (!IsAtProcessExit()) VFree(tempPtr->m_pAddr[i]); } } diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index d7d5257ea1836..c7f19158bdbdb 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -396,6 +396,12 @@ BOOL g_singleVersionHosting; typedef BOOL(WINAPI* PINITIALIZECONTEXT2)(PVOID Buffer, DWORD ContextFlags, PCONTEXT* Context, PDWORD ContextLength, ULONG64 XStateCompactionMask); PINITIALIZECONTEXT2 g_pfnInitializeContext2 = NULL; +static BOOLEAN WINAPI RtlDllShutdownInProgressFallback() +{ + return g_fProcessDetach; +} +PRTLDLLSHUTDOWNINPROGRESS g_pfnRtlDllShutdownInProgress = &RtlDllShutdownInProgressFallback; + #ifdef TARGET_X86 typedef VOID(__cdecl* PRTLRESTORECONTEXT)(PCONTEXT ContextRecord, struct _EXCEPTION_RECORD* ExceptionRecord); PRTLRESTORECONTEXT g_pfnRtlRestoreContext = NULL; @@ -406,8 +412,12 @@ void InitializeOptionalWindowsAPIPointers() HMODULE hm = GetModuleHandleW(_T("kernel32.dll")); g_pfnInitializeContext2 = (PINITIALIZECONTEXT2)GetProcAddress(hm, "InitializeContext2"); -#ifdef TARGET_X86 hm = GetModuleHandleW(_T("ntdll.dll")); + PRTLDLLSHUTDOWNINPROGRESS pfn = (PRTLDLLSHUTDOWNINPROGRESS)GetProcAddress(hm, "RtlDllShutdownInProgress"); + if (pfn != NULL) + g_pfnRtlDllShutdownInProgress = pfn; + +#ifdef TARGET_X86 g_pfnRtlRestoreContext = (PRTLRESTORECONTEXT)GetProcAddress(hm, "RtlRestoreContext"); #endif //TARGET_X86 } @@ -1159,11 +1169,6 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) Thread * pThisThread = GetThreadNULLOk(); #endif - // If the process is detaching then set the global state. - // This is used to get around FreeLibrary problems. - if(fIsDllUnloading) - g_fProcessDetach = true; - if (IsDbgHelperSpecialThread()) { // Our debugger helper thread does not allow Thread object to be set up. @@ -1187,7 +1192,7 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) // ungracefully. This check is an attempt to recognize that case // and avoid the impending hang when attempting to get the helper // thread to do things for us. - if ((g_pDebugInterface != NULL) && g_fProcessDetach) + if ((g_pDebugInterface != NULL) && IsAtProcessExit()) g_pDebugInterface->EarlyHelperThreadDeath(); #endif // DEBUGGING_SUPPORTED @@ -1204,7 +1209,7 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) // Indicate the EE is the shut down phase. g_fEEShutDown |= ShutDown_Start; - if (!g_fProcessDetach && !g_fFastExitProcess) + if (!IsAtProcessExit() && !g_fFastExitProcess) { g_fEEShutDown |= ShutDown_Finalize1; @@ -1214,7 +1219,7 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) } // Ok. Let's stop the EE. - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { // Convert key locks into "shutdown" mode. A lock in shutdown mode means: // - Only the finalizer/helper/shutdown threads will be able to take the lock. @@ -1316,7 +1321,7 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) // are already in use, then skip phase 2. This will happen only when those locks // are orphaned. In Vista, the penalty for attempting to enter such locks is // instant process termination. - if (g_fProcessDetach) + if (IsAtProcessExit()) { // The assert below is a bit too aggressive and has generally brought cases that have been race conditions // and not easily reproed to validate a bug. A typical race scenario is when there are two threads, @@ -1382,7 +1387,7 @@ void STDMETHODCALLTYPE EEShutDownHelper(BOOL fIsDllUnloading) EX_END_CATCH(SwallowAllExceptions); ClrFlsClearThreadType(ThreadType_Shutdown); - if (!g_fProcessDetach) + if (!IsAtProcessExit()) { g_pEEShutDownEvent->Set(); } @@ -1478,17 +1483,6 @@ BOOL IsThreadInSTA() // Actual shut down logic is factored out to EEShutDownHelper which may be called // directly by EEShutDown, or indirectly on another thread (see code:#STAShutDown). // -// In order that callees may also know the value of fIsDllUnloading, EEShutDownHelper -// sets g_fProcessDetach = fIsDllUnloading, and g_fProcessDetach may then be retrieved -// via code:IsAtProcessExit. -// -// NOTE 1: Actually, g_fProcessDetach is set to TRUE if fIsDllUnloading is TRUE. But -// g_fProcessDetach doesn't appear to be explicitly set to FALSE. (Apparently -// g_fProcessDetach is implicitly initialized to FALSE as clr.dll is loaded.) -// -// NOTE 2: EEDllMain(DLL_PROCESS_DETACH) already sets g_fProcessDetach to TRUE, so it -// appears EEShutDownHelper doesn't have to. -// void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading) { CONTRACTL { @@ -1729,25 +1723,13 @@ struct TlsDestructionMonitor thread->m_pFrame = FRAME_TOP; GCX_COOP_NO_DTOR_END(); } -#ifdef _DEBUG - BOOL oldGCOnTransitionsOK = thread->m_GCOnTransitionsOK; - thread->m_GCOnTransitionsOK = FALSE; -#endif - if (!IsAtProcessExit() && !g_fEEShutDown) - { - GCX_COOP_NO_DTOR(); - FreeThreadStaticData(&t_ThreadStatics, thread); - GCX_COOP_NO_DTOR_END(); - } -#ifdef _DEBUG - thread->m_GCOnTransitionsOK = oldGCOnTransitionsOK; -#endif + thread->DetachThread(TRUE); } else { // Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up - AssertThreadStaticDataFreed(&t_ThreadStatics); + AssertThreadStaticDataFreed(); } ThreadDetaching(); diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index fc8dfbb2778d1..d019affc33b33 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -3550,7 +3550,7 @@ void ExecutionManager::CleanupCodeHeaps() } CONTRACTL_END; - _ASSERTE (g_fProcessDetach || (GCHeapUtilities::IsGCInProgress() && ::IsGCThread())); + _ASSERTE (IsAtProcessExit() || (GCHeapUtilities::IsGCInProgress() && ::IsGCThread())); GetEEJitManager()->CleanupCodeHeaps(); } @@ -3564,7 +3564,7 @@ void EEJitManager::CleanupCodeHeaps() } CONTRACTL_END; - _ASSERTE (g_fProcessDetach || (GCHeapUtilities::IsGCInProgress() && ::IsGCThread())); + _ASSERTE (IsAtProcessExit() || (GCHeapUtilities::IsGCInProgress() && ::IsGCThread())); // Quick out, don't even take the lock if we have not cleanup to do. // This is important because ETW takes the CodeHeapLock when it is doing diff --git a/src/coreclr/vm/comcache.cpp b/src/coreclr/vm/comcache.cpp index 573ead1a96e35..938e1f57b0ef9 100644 --- a/src/coreclr/vm/comcache.cpp +++ b/src/coreclr/vm/comcache.cpp @@ -504,7 +504,7 @@ VOID IUnkEntry::ReleaseInterface(RCW *pRCW) } CONTRACTL_END; - if (g_fProcessDetach) + if (IsAtProcessExit()) { // The Release call is unsafe if the process is going away (calls into // DLLs we don't know are even mapped). @@ -533,14 +533,14 @@ VOID IUnkEntry::Free() NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; - PRECONDITION(g_fProcessDetach || m_pUnknown == (IUnknown *)0xBADF00D); + PRECONDITION(IsAtProcessExit() || m_pUnknown == (IUnknown *)0xBADF00D); } CONTRACTL_END; // Log the de-allocation of the IUnknown entry. LOG((LF_INTEROP, LL_INFO10000, "IUnkEntry::Free called for context 0x%08X, to release entry with m_pUnknown %p, on thread %p\n", m_pCtxCookie, m_pUnknown, GetThreadNULLOk())); - if (g_fProcessDetach) + if (IsAtProcessExit()) { IStream *pOldStream = m_pStream; if (InterlockedExchangeT(&m_pStream, NULL) == pOldStream) @@ -846,7 +846,7 @@ HRESULT IUnkEntry::MarshalIUnknownToStreamCallback2(LPVOID pData) GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pData)); - PRECONDITION(g_fProcessDetach == FALSE); + PRECONDITION(IsAtProcessExit() == FALSE); } CONTRACTL_END; @@ -967,7 +967,7 @@ HRESULT IUnkEntry::MarshalIUnknownToStreamCallback(LPVOID pData) GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pData)); - PRECONDITION(g_fProcessDetach == FALSE); + PRECONDITION(IsAtProcessExit() == FALSE); } CONTRACTL_END; @@ -1199,7 +1199,7 @@ CtxEntry::~CtxEntry() CONTRACTL_END; // If the context is a valid context then release it. - if (m_pObjCtx && !g_fProcessDetach) + if (m_pObjCtx && !IsAtProcessExit()) { SafeRelease(m_pObjCtx); m_pObjCtx = NULL; @@ -1304,7 +1304,7 @@ HRESULT CtxEntry::EnterContext(PFNCTXCALLBACK pCallbackFunc, LPVOID pData) // If we are in process detach, we cannot safely try to enter another context // since we don't know if OLE32 is still loaded. - if (g_fProcessDetach) + if (IsAtProcessExit()) { LOG((LF_INTEROP, LL_INFO100, "Entering into context 0x08%x has failed since we are in process detach\n", m_pCtxCookie)); return RPC_E_DISCONNECTED; diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index b80c2442c9097..9cefbdfaa5921 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -3103,7 +3103,7 @@ void ComMethodTable::Cleanup() if (m_pDispatchInfo) delete m_pDispatchInfo; - if (m_pITypeInfo && !g_fProcessDetach) + if (m_pITypeInfo && !IsAtProcessExit()) SafeRelease(m_pITypeInfo); // The m_pMDescr and the current instance is allocated from the related LoaderAllocator diff --git a/src/coreclr/vm/crst.cpp b/src/coreclr/vm/crst.cpp index 3eacfc29f76a1..c313a710517e4 100644 --- a/src/coreclr/vm/crst.cpp +++ b/src/coreclr/vm/crst.cpp @@ -392,7 +392,7 @@ void CrstBase::PreEnter() STATIC_CONTRACT_GC_NOTRIGGER; // Are we in the shutdown sequence and in phase 2 of it? - if (g_fProcessDetach && (g_fEEShutDown & ShutDown_Phase2)) + if (IsAtProcessExit() && (g_fEEShutDown & ShutDown_Phase2)) { // Ensure that this lock has been flagged to be taken during shutdown _ASSERTE_MSG(CanBeTakenDuringShutdown(), "Attempting to take a lock at shutdown that is not CRST_TAKEN_DURING_SHUTDOWN"); @@ -569,7 +569,7 @@ void CrstBase::PreLeave() } // Are we in the shutdown sequence and in phase 2 of it? - if (g_fProcessDetach && (g_fEEShutDown & ShutDown_Phase2)) + if (IsAtProcessExit() && (g_fEEShutDown & ShutDown_Phase2)) { // Ensure that this lock has been flagged to be taken during shutdown _ASSERTE_MSG(CanBeTakenDuringShutdown(), "Attempting to leave a lock at shutdown that is not CRST_TAKEN_DURING_SHUTDOWN"); diff --git a/src/coreclr/vm/crst.h b/src/coreclr/vm/crst.h index 5928812dc49f5..65769569c4bea 100644 --- a/src/coreclr/vm/crst.h +++ b/src/coreclr/vm/crst.h @@ -97,10 +97,6 @@ #define ShutDown_IUnknown 0x00000040 #define ShutDown_Phase2 0x00000080 -#ifndef DACCESS_COMPILE -extern bool g_fProcessDetach; -extern DWORD g_fEEShutDown; -#endif // Total count of Crst lock of the type (Shutdown) that are currently in use extern Volatile g_ShutdownCrstUsageCount; diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 95c568f50acbf..7f66adf7df631 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -725,7 +725,7 @@ bool ep_rt_process_detach (void) { STATIC_CONTRACT_NOTHROW; - return (bool)g_fProcessDetach; + return (bool)IsAtProcessExit(); } static diff --git a/src/coreclr/vm/interoputil.cpp b/src/coreclr/vm/interoputil.cpp index 587b5a0e5f36f..5b9224ce041f4 100644 --- a/src/coreclr/vm/interoputil.cpp +++ b/src/coreclr/vm/interoputil.cpp @@ -1196,7 +1196,7 @@ void MinorCleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) NOTHROW; GC_NOTRIGGER; MODE_ANY; - PRECONDITION( GCHeapUtilities::IsGCInProgress() || ( (g_fEEShutDown & ShutDown_SyncBlock) && g_fProcessDetach ) ); + PRECONDITION( GCHeapUtilities::IsGCInProgress() || ( (g_fEEShutDown & ShutDown_SyncBlock) && IsAtProcessExit() ) ); } CONTRACTL_END; @@ -1225,7 +1225,7 @@ void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) } CONTRACTL_END; - if ((g_fEEShutDown & ShutDown_SyncBlock) && g_fProcessDetach ) + if ((g_fEEShutDown & ShutDown_SyncBlock) && IsAtProcessExit() ) MinorCleanupSyncBlockComData(pInteropInfo); #ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION diff --git a/src/coreclr/vm/runtimecallablewrapper.cpp b/src/coreclr/vm/runtimecallablewrapper.cpp index aa28bfa6d8376..21fa3b8879f5b 100644 --- a/src/coreclr/vm/runtimecallablewrapper.cpp +++ b/src/coreclr/vm/runtimecallablewrapper.cpp @@ -1709,7 +1709,7 @@ void RCW::MinorCleanup() NOTHROW; GC_NOTRIGGER; MODE_ANY; - PRECONDITION(GCHeapUtilities::IsGCInProgress() || ( (g_fEEShutDown & ShutDown_SyncBlock) && g_fProcessDetach )); + PRECONDITION(GCHeapUtilities::IsGCInProgress() || ( (g_fEEShutDown & ShutDown_SyncBlock) && IsAtProcessExit() )); } CONTRACTL_END; diff --git a/src/coreclr/vm/stdinterfaces.cpp b/src/coreclr/vm/stdinterfaces.cpp index 3131f33d2892b..64a337e829ee7 100644 --- a/src/coreclr/vm/stdinterfaces.cpp +++ b/src/coreclr/vm/stdinterfaces.cpp @@ -222,7 +222,7 @@ Unknown_AddRef_Internal(IUnknown* pUnk) if (pSimpleWrap && (pOuter = pSimpleWrap->GetOuter()) != NULL) { // If we are in process detach, we cannot safely call release on our outer. - if (g_fProcessDetach) + if (IsAtProcessExit()) return 1; ULONG cbRef = pOuter->AddRef(); @@ -284,7 +284,7 @@ Unknown_Release_Internal(IUnknown* pUnk) if (pSimpleWrap && (pOuter = pSimpleWrap->GetOuter()) != NULL) { // If we are in process detach, we cannot safely call release on our outer. - if (g_fProcessDetach) + if (IsAtProcessExit()) cbRef = 1; cbRef = SafeReleasePreemp(pOuter); diff --git a/src/coreclr/vm/stringliteralmap.cpp b/src/coreclr/vm/stringliteralmap.cpp index 34f4eecea5015..168ab5503f4ca 100644 --- a/src/coreclr/vm/stringliteralmap.cpp +++ b/src/coreclr/vm/stringliteralmap.cpp @@ -341,7 +341,7 @@ GlobalStringLiteralMap::~GlobalStringLiteralMap() { // We are shutting down, the OS will reclaim the memory from the StringLiteralEntries, // m_MemoryPool and m_StringToEntryHashTable. - _ASSERTE(g_fProcessDetach); + _ASSERTE(IsAtProcessExit()); } } diff --git a/src/coreclr/vm/syncclean.cpp b/src/coreclr/vm/syncclean.cpp index 142417ce1d12c..7247529da56cd 100644 --- a/src/coreclr/vm/syncclean.cpp +++ b/src/coreclr/vm/syncclean.cpp @@ -66,7 +66,7 @@ void SyncClean::CleanUp () LIMITED_METHOD_CONTRACT; // Only GC thread can call this. - _ASSERTE (g_fProcessDetach || + _ASSERTE (IsAtProcessExit() || IsGCSpecialThread() || (GCHeapUtilities::IsGCInProgress() && GetThreadNULLOk() == ThreadSuspend::GetSuspensionThread())); if (m_HashMap) diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index abd6bac8050f9..2c8e9668a63d7 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -854,22 +854,6 @@ void DestroyThread(Thread *th) th->UnmarkThreadForAbort(); } - // Clear any outstanding stale EH state that maybe still active on the thread. -#ifdef FEATURE_EH_FUNCLETS - ExceptionTracker::PopTrackers((void*)-1); -#else // !FEATURE_EH_FUNCLETS -#ifdef TARGET_X86 - PTR_ThreadExceptionState pExState = th->GetExceptionState(); - if (pExState->IsExceptionInProgress()) - { - GCX_COOP(); - pExState->GetCurrentExceptionTracker()->UnwindExInfo((void *)-1); - } -#else // !TARGET_X86 -#error Unsupported platform -#endif // TARGET_X86 -#endif // FEATURE_EH_FUNCLETS - if (g_fEEShutDown == 0) { th->SetThreadState(Thread::TS_ReportDead); @@ -890,22 +874,6 @@ HRESULT Thread::DetachThread(BOOL fDLLThreadDetach) STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; - // Clear any outstanding stale EH state that maybe still active on the thread. -#ifdef FEATURE_EH_FUNCLETS - ExceptionTracker::PopTrackers((void*)-1); -#else // !FEATURE_EH_FUNCLETS -#ifdef TARGET_X86 - PTR_ThreadExceptionState pExState = GetExceptionState(); - if (pExState->IsExceptionInProgress()) - { - GCX_COOP(); - pExState->GetCurrentExceptionTracker()->UnwindExInfo((void *)-1); - } -#else // !TARGET_X86 -#error Unsupported platform -#endif // TARGET_X86 -#endif // FEATURE_EH_FUNCLETS - #ifdef FEATURE_COMINTEROP IErrorInfo *pErrorInfo; // Avoid calling GetErrorInfo() if ole32 has already executed the DLL_THREAD_DETACH, @@ -962,19 +930,7 @@ HRESULT Thread::DetachThread(BOOL fDLLThreadDetach) m_ThreadHandleForClose = hThread; } - if (GCHeapUtilities::IsGCHeapInitialized()) - { - // If the GC heap is initialized, we need to fix the alloc context for this detaching thread. - GCX_COOP(); - // GetTotalAllocatedBytes reads dead_threads_non_alloc_bytes, but will suspend EE, being in COOP mode we cannot race with that - // however, there could be other threads terminating and doing the same Add. - InterlockedExchangeAdd64((LONG64*)&dead_threads_non_alloc_bytes, t_runtime_thread_locals.alloc_context.alloc_limit - t_runtime_thread_locals.alloc_context.alloc_ptr); - GCHeapUtilities::GetGCHeap()->FixAllocContext(&t_runtime_thread_locals.alloc_context, NULL, NULL); - t_runtime_thread_locals.alloc_context.init(); // re-initialize the context. - - // Clear out the alloc context pointer for this thread. When TLS is gone, this pointer will point into freed memory. - m_pRuntimeThreadLocals = nullptr; - } + CooperativeCleanup(); // We need to make sure that TLS are touched last here. SetThread(NULL); @@ -2774,6 +2730,52 @@ void Thread::CleanupCOMState() } #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT +// Thread cleanup that must be run in cooperative mode before the thread is destroyed. +void Thread::CooperativeCleanup() +{ + CONTRACTL { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + // Skip the cleanup during process exit. Switch to cooperative mode can deadlock during process exit on Windows. + if (IsAtProcessExit()) + return; + + GCX_COOP(); + + // Clear any outstanding stale EH state that maybe still active on the thread. +#ifdef FEATURE_EH_FUNCLETS + ExceptionTracker::PopTrackers((void*)-1); +#else // !FEATURE_EH_FUNCLETS + PTR_ThreadExceptionState pExState = GetExceptionState(); + if (pExState->IsExceptionInProgress()) + { + pExState->GetCurrentExceptionTracker()->UnwindExInfo((void *)-1); + } +#endif // FEATURE_EH_FUNCLETS + + if (m_ThreadLocalDataPtr != NULL) + { + FreeThreadStaticData(this); + m_ThreadLocalDataPtr = NULL; + } + + if (GCHeapUtilities::IsGCHeapInitialized()) + { + // If the GC heap is initialized, we need to fix the alloc context for this detaching thread. + // GetTotalAllocatedBytes reads dead_threads_non_alloc_bytes, but will suspend EE, being in COOP mode we cannot race with that + // however, there could be other threads terminating and doing the same Add. + InterlockedExchangeAdd64((LONG64*)&dead_threads_non_alloc_bytes, t_runtime_thread_locals.alloc_context.alloc_limit - t_runtime_thread_locals.alloc_context.alloc_ptr); + GCHeapUtilities::GetGCHeap()->FixAllocContext(&t_runtime_thread_locals.alloc_context, NULL, NULL); + t_runtime_thread_locals.alloc_context.init(); // re-initialize the context. + + // Clear out the alloc context pointer for this thread. When TLS is gone, this pointer will point into freed memory. + m_pRuntimeThreadLocals = nullptr; + } +} + // See general comments on thread destruction (code:#threadDestruction) above. void Thread::OnThreadTerminate(BOOL holdingLock) { @@ -2809,6 +2811,11 @@ void Thread::OnThreadTerminate(BOOL holdingLock) } #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + if (this == GetThreadNULLOk()) + { + CooperativeCleanup(); + } + if (g_fEEShutDown != 0) { // We have started shutdown. Not safe to touch CLR state. @@ -2836,24 +2843,8 @@ void Thread::OnThreadTerminate(BOOL holdingLock) // Destroy the LastThrown handle (and anything that violates the above assert). SafeSetThrowables(NULL); - // Free all structures related to thread statics for this thread - DeleteThreadStaticData(); - - } - - if (GCHeapUtilities::IsGCHeapInitialized()) - { - // Guaranteed to NOT be a shutdown case, because we tear down the heap before - // we tear down any threads during shutdown. - if (ThisThreadID == CurrentThreadID && GetAllocContext() != nullptr) - { - GCX_COOP(); - // GetTotalAllocatedBytes reads dead_threads_non_alloc_bytes, but will suspend EE, being in COOP mode we cannot race with that - // however, there could be other threads terminating and doing the same Add. - InterlockedExchangeAdd64((LONG64*)&dead_threads_non_alloc_bytes, GetAllocContext()->alloc_limit - GetAllocContext()->alloc_ptr); - GCHeapUtilities::GetGCHeap()->FixAllocContext(GetAllocContext(), NULL, NULL); - GetAllocContext()->init(); // re-initialize the context. - } + // Free loader allocator structures related to this thread + FreeLoaderAllocatorHandlesForTLSData(this); } // We switch a thread to dead when it has finished doing useful work. But it @@ -2950,14 +2941,6 @@ void Thread::OnThreadTerminate(BOOL holdingLock) // If nobody else is holding onto the thread, we may destruct it here: ULONG oldCount = DecExternalCount(TRUE); - // If we are shutting down the process, we only have one thread active in the - // system. So we can disregard all the reasons that hold this thread alive -- - // TLS is about to be reclaimed anyway. - if (IsAtProcessExit()) - while (oldCount > 0) - { - oldCount = DecExternalCount(TRUE); - } // ASSUME THAT THE THREAD IS DELETED, FROM HERE ON @@ -7590,32 +7573,6 @@ Frame * Thread::NotifyFrameChainOfExceptionUnwind(Frame* pStartFrame, LPVOID pvL return pFrame; } -//+---------------------------------------------------------------------------- -// -// Method: Thread::DeleteThreadStaticData private -// -// Synopsis: Delete the static data for each appdomain that this thread -// visited. -// -// -//+---------------------------------------------------------------------------- - -void Thread::DeleteThreadStaticData() -{ - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - FreeLoaderAllocatorHandlesForTLSData(this); - if (!IsAtProcessExit() && !g_fEEShutDown) - { - FreeThreadStaticData(m_ThreadLocalDataPtr, this); - } -} - OBJECTREF Thread::GetCulture(BOOL bUICulture) { CONTRACTL { diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index ae9c584e21cec..429031cf5493a 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -1180,6 +1180,8 @@ class Thread void BaseWinRTUninitialize(); #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + void CooperativeCleanup(); + void OnThreadTerminate(BOOL holdingLock); static void CleanupDetachedThreads(); @@ -3339,12 +3341,6 @@ class Thread SpinLock m_TlsSpinLock; PTR_ThreadLocalData GetThreadLocalDataPtr() { LIMITED_METHOD_DAC_CONTRACT; return m_ThreadLocalDataPtr; } -public: - - // Called during Thread death to clean up all structures - // associated with thread statics - void DeleteThreadStaticData(); - private: TailCallTls m_tailCallTls; diff --git a/src/coreclr/vm/threadstatics.cpp b/src/coreclr/vm/threadstatics.cpp index 23c38f6581863..0fd8f389f1a7d 100644 --- a/src/coreclr/vm/threadstatics.cpp +++ b/src/coreclr/vm/threadstatics.cpp @@ -408,22 +408,21 @@ void FreeLoaderAllocatorHandlesForTLSData(Thread *pThread) } } -void AssertThreadStaticDataFreed(ThreadLocalData *pThreadLocalData) +void AssertThreadStaticDataFreed() { LIMITED_METHOD_CONTRACT; - if (!IsAtProcessExit() && !g_fEEShutDown) - { - _ASSERTE(pThreadLocalData->pThread == NULL); - _ASSERTE(pThreadLocalData->pCollectibleTlsArrayData == NULL); - _ASSERTE(pThreadLocalData->cCollectibleTlsData == 0); - _ASSERTE(pThreadLocalData->pNonCollectibleTlsArrayData == NULL); - _ASSERTE(pThreadLocalData->cNonCollectibleTlsData == 0); - _ASSERTE(pThreadLocalData->pInFlightData == NULL); - } + ThreadLocalData *pThreadLocalData = &t_ThreadStatics; + + _ASSERTE(pThreadLocalData->pThread == NULL); + _ASSERTE(pThreadLocalData->pCollectibleTlsArrayData == NULL); + _ASSERTE(pThreadLocalData->cCollectibleTlsData == 0); + _ASSERTE(pThreadLocalData->pNonCollectibleTlsArrayData == NULL); + _ASSERTE(pThreadLocalData->cNonCollectibleTlsData == 0); + _ASSERTE(pThreadLocalData->pInFlightData == NULL); } -void FreeThreadStaticData(ThreadLocalData *pThreadLocalData, Thread* pThread) +void FreeThreadStaticData(Thread* pThread) { CONTRACTL { NOTHROW; @@ -432,47 +431,34 @@ void FreeThreadStaticData(ThreadLocalData *pThreadLocalData, Thread* pThread) } CONTRACTL_END; - if (pThreadLocalData == NULL) - return; - - if (!IsAtProcessExit() && !g_fEEShutDown) - { - SpinLockHolder spinLock(&pThread->m_TlsSpinLock); - - if (pThreadLocalData->pThread == NULL) - { - return; - } - - pThreadLocalData = pThread->m_ThreadLocalDataPtr; + SpinLockHolder spinLock(&pThread->m_TlsSpinLock); - if (pThreadLocalData == NULL) - return; + ThreadLocalData *pThreadLocalData = &t_ThreadStatics; - for (int32_t iTlsSlot = 0; iTlsSlot < pThreadLocalData->cCollectibleTlsData; ++iTlsSlot) + for (int32_t iTlsSlot = 0; iTlsSlot < pThreadLocalData->cCollectibleTlsData; ++iTlsSlot) + { + if (!IsHandleNullUnchecked(pThreadLocalData->pCollectibleTlsArrayData[iTlsSlot])) { - if (!IsHandleNullUnchecked(pThreadLocalData->pCollectibleTlsArrayData[iTlsSlot])) - { - DestroyLongWeakHandle(pThreadLocalData->pCollectibleTlsArrayData[iTlsSlot]); - } + DestroyLongWeakHandle(pThreadLocalData->pCollectibleTlsArrayData[iTlsSlot]); } + } - delete[] (uint8_t*)pThreadLocalData->pCollectibleTlsArrayData; + delete[] (uint8_t*)pThreadLocalData->pCollectibleTlsArrayData; - pThreadLocalData->pCollectibleTlsArrayData = 0; - pThreadLocalData->cCollectibleTlsData = 0; - pThreadLocalData->pNonCollectibleTlsArrayData = 0; - pThreadLocalData->cNonCollectibleTlsData = 0; + pThreadLocalData->pCollectibleTlsArrayData = 0; + pThreadLocalData->cCollectibleTlsData = 0; + pThreadLocalData->pNonCollectibleTlsArrayData = 0; + pThreadLocalData->cNonCollectibleTlsData = 0; - while (pThreadLocalData->pInFlightData != NULL) - { - InFlightTLSData* pInFlightData = pThreadLocalData->pInFlightData; - pThreadLocalData->pInFlightData = pInFlightData->pNext; - delete pInFlightData; - } - pThreadLocalData->pThread->m_ThreadLocalDataPtr = NULL; - VolatileStoreWithoutBarrier(&pThreadLocalData->pThread, (Thread*)NULL); + while (pThreadLocalData->pInFlightData != NULL) + { + InFlightTLSData* pInFlightData = pThreadLocalData->pInFlightData; + pThreadLocalData->pInFlightData = pInFlightData->pNext; + delete pInFlightData; } + + _ASSERTE(pThreadLocalData->pThread == pThread); + pThreadLocalData->pThread = NULL; } void SetTLSBaseValue(TADDR *ppTLSBaseAddress, TADDR pTLSBaseAddress, bool useGCBarrierInsteadOfHandleStore) diff --git a/src/coreclr/vm/threadstatics.h b/src/coreclr/vm/threadstatics.h index 0422629e05f9c..3ab3806cbf952 100644 --- a/src/coreclr/vm/threadstatics.h +++ b/src/coreclr/vm/threadstatics.h @@ -329,8 +329,8 @@ PTR_MethodTable LookupMethodTableForThreadStaticKnownToBeAllocated(TLSIndex inde void InitializeThreadStaticData(); void InitializeCurrentThreadsStaticData(Thread* pThread); void FreeLoaderAllocatorHandlesForTLSData(Thread* pThread); -void FreeThreadStaticData(ThreadLocalData *pThreadLocalData, Thread* pThread); -void AssertThreadStaticDataFreed(ThreadLocalData *pThreadLocalData); +void FreeThreadStaticData(Thread* pThread); +void AssertThreadStaticDataFreed(); void GetTLSIndexForThreadStatic(MethodTable* pMT, bool gcStatic, TLSIndex* pIndex, uint32_t bytesNeeded); void FreeTLSIndicesForLoaderAllocator(LoaderAllocator *pLoaderAllocator); void* GetThreadLocalStaticBase(TLSIndex index); diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index 4fd77e488a249..9cdb868998433 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -5543,7 +5543,7 @@ void ThreadSuspend::SuspendEE(SUSPEND_REASON reason) // set tls flags for compat with SOS ClrFlsSetThreadType(ThreadType_DynamicSuspendEE); - _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + _ASSERTE(ThreadStore::HoldingThreadStore() || IsAtProcessExit()); #ifdef PROFILING_SUPPORTED // If the profiler desires information about GCs, then let it know that one diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index 30e9e60d69155..5aa48135ae30e 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -459,12 +459,24 @@ GVAL_DECL(bool, g_metadataUpdatesApplied); #endif EXTERN bool g_fManagedAttach; +#ifdef HOST_WINDOWS +typedef BOOLEAN (WINAPI* PRTLDLLSHUTDOWNINPROGRESS)(); +EXTERN PRTLDLLSHUTDOWNINPROGRESS g_pfnRtlDllShutdownInProgress; +#endif + // Indicates whether we're executing shut down as a result of DllMain // (DLL_PROCESS_DETACH). See comments at code:EEShutDown for details. -inline BOOL IsAtProcessExit() +inline bool IsAtProcessExit() { SUPPORTS_DAC; +#if defined(DACCESS_COMPILE) || !defined(HOST_WINDOWS) return g_fProcessDetach; +#else + // RtlDllShutdownInProgress provides more accurace information about whether the process is shutting down. + // Use it if it is available to avoid shutdown deadlocks. + // https://learn.microsoft.com/windows/win32/devnotes/rtldllshutdowninprogress + return g_pfnRtlDllShutdownInProgress(); +#endif } enum FWStatus