Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use RtlDllShutdownInProgress to detect process shutdown on Windows #103877

Merged
merged 2 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 13 additions & 31 deletions src/coreclr/vm/ceemain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
151 changes: 54 additions & 97 deletions src/coreclr/vm/threads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 2 additions & 6 deletions src/coreclr/vm/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,8 @@ class Thread
void BaseWinRTUninitialize();
#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT

void CooperativeCleanup();

void OnThreadTerminate(BOOL holdingLock);

static void CleanupDetachedThreads();
Expand Down Expand Up @@ -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;

Expand Down
Loading