Skip to content

Commit fa3beb9

Browse files
VSadovjkotas
andauthored
Use FLS detach callback as a thread termination notification. Another try. (#112809)
* Use FLS detach as thread termination notification on windows. * use _ASSERTE_ALL_BUILDS * one more case * InitFlsSlot throws per convention used in threading initialization * OsDetachThread could be void * Update src/coreclr/vm/ceemain.cpp * ensure CoInitialize * Asserts to fail deterministically. * comments * scope the failfast to Windows and tweak the comments a bit. * better assert * Apply suggestions from code review Co-authored-by: Jan Kotas <[email protected]> * Undo unnecessary finalizer thread changes * reverse the failfast condition * handle destruction of unattached threads * PR feedback Co-authored-by: Jan Kotas <[email protected]> * Update src/coreclr/vm/ceemain.cpp * set g_fComStarted as soon as we call CoInitialize * Naming nit * fix a typpo + better comment --------- Co-authored-by: Jan Kotas <[email protected]>
1 parent 80b0e17 commit fa3beb9

File tree

8 files changed

+190
-64
lines changed

8 files changed

+190
-64
lines changed

src/coreclr/nativeaot/Runtime/threadstore.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ void ThreadStore::DetachCurrentThread()
157157
}
158158

159159
// Unregister from OS notifications
160-
// This can return false if detach notification is spurious and does not belong to this thread.
160+
// This can return false if a thread did not register for OS notification.
161161
if (!PalDetachThread(pDetachingThread))
162162
{
163163
return;

src/coreclr/vm/ceemain.cpp

Lines changed: 142 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,10 @@ void EEStartupHelper()
872872
}
873873
#endif
874874

875+
// This isn't done as part of InitializeGarbageCollector() above because
876+
// debugger must be initialized before creating EE thread objects
877+
FinalizerThread::FinalizerThreadCreate();
878+
875879
InitPreStubManager();
876880

877881
#ifdef FEATURE_COMINTEROP
@@ -909,10 +913,6 @@ void EEStartupHelper()
909913
#endif // FEATURE_PERFTRACING
910914
GenAnalysis::Initialize();
911915

912-
// This isn't done as part of InitializeGarbageCollector() above because thread
913-
// creation requires AppDomains to have been set up.
914-
FinalizerThread::FinalizerThreadCreate();
915-
916916
// Now we really have fully initialized the garbage collector
917917
SetGarbageCollectorFullyInitialized();
918918

@@ -964,6 +964,12 @@ void EEStartupHelper()
964964
g_MiniMetaDataBuffMaxSize, MEM_COMMIT, PAGE_READWRITE);
965965
#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
966966

967+
#ifdef TARGET_WINDOWS
968+
// By now finalizer thread should have initialized FLS slot for thread cleanup notifications.
969+
// And ensured that COM is initialized (must happen before allocating FLS slot).
970+
// Make sure that this was done.
971+
FinalizerThread::WaitForFinalizerThreadStart();
972+
#endif
967973
g_fEEStarted = TRUE;
968974
g_EEStartupStatus = S_OK;
969975
hr = S_OK;
@@ -1684,6 +1690,135 @@ BOOL STDMETHODCALLTYPE EEDllMain( // TRUE on success, FALSE on error.
16841690

16851691
#endif // !defined(CORECLR_EMBEDDED)
16861692

1693+
static void RuntimeThreadShutdown(void* thread)
1694+
{
1695+
Thread* pThread = (Thread*)thread;
1696+
_ASSERTE(pThread == GetThreadNULLOk());
1697+
1698+
if (pThread)
1699+
{
1700+
#ifdef FEATURE_COMINTEROP
1701+
// reset the CoInitialize state
1702+
// so we don't call CoUninitialize during thread detach
1703+
pThread->ResetCoInitialized();
1704+
#endif // FEATURE_COMINTEROP
1705+
// For case where thread calls ExitThread directly, we need to reset the
1706+
// frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode.
1707+
// We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode
1708+
if (pThread->m_pFrame != FRAME_TOP)
1709+
{
1710+
#ifdef _DEBUG
1711+
pThread->m_GCOnTransitionsOK = FALSE;
1712+
#endif
1713+
GCX_COOP_NO_DTOR();
1714+
pThread->m_pFrame = FRAME_TOP;
1715+
GCX_COOP_NO_DTOR_END();
1716+
}
1717+
1718+
pThread->DetachThread(TRUE);
1719+
}
1720+
else
1721+
{
1722+
// Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up
1723+
AssertThreadStaticDataFreed();
1724+
}
1725+
1726+
ThreadDetaching();
1727+
}
1728+
1729+
#ifdef TARGET_WINDOWS
1730+
1731+
// Index for the fiber local storage of the attached thread pointer
1732+
static uint32_t g_flsIndex = FLS_OUT_OF_INDEXES;
1733+
1734+
#define FLS_STATE_CLEAR 0
1735+
#define FLS_STATE_ARMED 1
1736+
#define FLS_STATE_INVOKED 2
1737+
1738+
static __declspec(thread) byte t_flsState;
1739+
1740+
// This is called when each *fiber* is destroyed. When the home fiber of a thread is destroyed,
1741+
// it means that the thread itself is destroyed.
1742+
// Since we receive that notification outside of the Loader Lock, it allows us to safely acquire
1743+
// the ThreadStore lock in the RuntimeThreadShutdown.
1744+
static void __stdcall FiberDetachCallback(void* lpFlsData)
1745+
{
1746+
_ASSERTE(g_flsIndex != FLS_OUT_OF_INDEXES);
1747+
_ASSERTE(lpFlsData);
1748+
1749+
if (t_flsState == FLS_STATE_ARMED)
1750+
{
1751+
RuntimeThreadShutdown(lpFlsData);
1752+
}
1753+
1754+
t_flsState = FLS_STATE_INVOKED;
1755+
}
1756+
1757+
void InitFlsSlot()
1758+
{
1759+
// We use fiber detach callbacks to run our thread shutdown code because the fiber detach
1760+
// callback is made without the OS loader lock
1761+
g_flsIndex = FlsAlloc(FiberDetachCallback);
1762+
if (g_flsIndex == FLS_OUT_OF_INDEXES)
1763+
{
1764+
COMPlusThrowWin32();
1765+
}
1766+
}
1767+
1768+
// Register the thread with OS to be notified when thread is about to be destroyed
1769+
// It fails fast if a different thread was already registered with the current fiber.
1770+
// Parameters:
1771+
// thread - thread to attach
1772+
static void OsAttachThread(void* thread)
1773+
{
1774+
if (t_flsState == FLS_STATE_INVOKED)
1775+
{
1776+
_ASSERTE_ALL_BUILDS(!"Attempt to execute managed code after the .NET runtime thread state has been destroyed.");
1777+
}
1778+
1779+
t_flsState = FLS_STATE_ARMED;
1780+
1781+
// Associate the current fiber with the current thread. This makes the current fiber the thread's "home"
1782+
// fiber. This fiber is the only fiber allowed to execute managed code on this thread. When this fiber
1783+
// is destroyed, we consider the thread to be destroyed.
1784+
_ASSERTE(thread != NULL);
1785+
FlsSetValue(g_flsIndex, thread);
1786+
}
1787+
1788+
// Detach thread from OS notifications.
1789+
// It fails fast if some other thread value was attached to the current fiber.
1790+
// Parameters:
1791+
// thread - thread to detach
1792+
void OsDetachThread(void* thread)
1793+
{
1794+
ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES);
1795+
void* threadFromCurrentFiber = FlsGetValue(g_flsIndex);
1796+
1797+
if (threadFromCurrentFiber == NULL)
1798+
{
1799+
// Thread is not attached.
1800+
// This could come from DestroyThread called when refcount reaches 0
1801+
// and the thread may have already been detached or never attached.
1802+
// We leave t_flsState as-is to keep track whether our callback has been called.
1803+
return;
1804+
}
1805+
1806+
if (threadFromCurrentFiber != thread)
1807+
{
1808+
_ASSERTE_ALL_BUILDS(!"Detaching a thread from the wrong fiber");
1809+
}
1810+
1811+
// Leave the existing FLS value, to keep the callback "armed" so that we could observe the termination callback.
1812+
// After that we will not allow to attach as we will no longer be able to clean up.
1813+
t_flsState = FLS_STATE_CLEAR;
1814+
}
1815+
1816+
void EnsureTlsDestructionMonitor()
1817+
{
1818+
OsAttachThread(GetThread());
1819+
}
1820+
1821+
#else
16871822
struct TlsDestructionMonitor
16881823
{
16891824
bool m_activated = false;
@@ -1697,36 +1832,7 @@ struct TlsDestructionMonitor
16971832
{
16981833
if (m_activated)
16991834
{
1700-
Thread* thread = GetThreadNULLOk();
1701-
if (thread)
1702-
{
1703-
#ifdef FEATURE_COMINTEROP
1704-
// reset the CoInitialize state
1705-
// so we don't call CoUninitialize during thread detach
1706-
thread->ResetCoInitialized();
1707-
#endif // FEATURE_COMINTEROP
1708-
// For case where thread calls ExitThread directly, we need to reset the
1709-
// frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode.
1710-
// We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode
1711-
if (thread->m_pFrame != FRAME_TOP)
1712-
{
1713-
#ifdef _DEBUG
1714-
thread->m_GCOnTransitionsOK = FALSE;
1715-
#endif
1716-
GCX_COOP_NO_DTOR();
1717-
thread->m_pFrame = FRAME_TOP;
1718-
GCX_COOP_NO_DTOR_END();
1719-
}
1720-
1721-
thread->DetachThread(TRUE);
1722-
}
1723-
else
1724-
{
1725-
// Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up
1726-
AssertThreadStaticDataFreed();
1727-
}
1728-
1729-
ThreadDetaching();
1835+
RuntimeThreadShutdown(GetThreadNULLOk());
17301836
}
17311837
}
17321838
};
@@ -1740,6 +1846,8 @@ void EnsureTlsDestructionMonitor()
17401846
tls_destructionMonitor.Activate();
17411847
}
17421848

1849+
#endif
1850+
17431851
#ifdef DEBUGGING_SUPPORTED
17441852
//
17451853
// InitializeDebugger initialized the Runtime-side CLR Debugging Services

src/coreclr/vm/ceemain.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ void ForceEEShutdown(ShutdownCompleteAction sca = SCA_ExitProcessWhenShutdownCom
4646
void ThreadDetaching();
4747

4848
void EnsureTlsDestructionMonitor();
49+
#ifdef TARGET_WINDOWS
50+
void InitFlsSlot();
51+
void OsDetachThread(void* thread);
52+
#endif
4953

5054
void SetLatchedExitCode (INT32 code);
5155
INT32 GetLatchedExitCode (void);

src/coreclr/vm/finalizerthread.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,20 @@ DWORD WINAPI FinalizerThread::FinalizerThreadStart(void *args)
380380

381381
LOG((LF_GC, LL_INFO10, "Finalizer thread starting...\n"));
382382

383+
#ifdef TARGET_WINDOWS
384+
#ifdef FEATURE_COMINTEROP
385+
// Making finalizer thread MTA early ensures that COM is initialized before we initialize our thread
386+
// termination callback.
387+
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
388+
g_fComStarted = true;
389+
#endif
390+
391+
InitFlsSlot();
392+
393+
// handshake with EE initialization, as now we can attach Thread objects to native threads.
394+
hEventFinalizerDone->Set();
395+
#endif
396+
383397
s_FinalizerThreadOK = GetFinalizerThread()->HasStarted();
384398

385399
_ASSERTE(s_FinalizerThreadOK);
@@ -491,6 +505,15 @@ void FinalizerThread::SignalFinalizationDone(int observedFullGcCount)
491505
hEventFinalizerDone->Set();
492506
}
493507

508+
void FinalizerThread::WaitForFinalizerThreadStart()
509+
{
510+
// this should be only called during EE startup
511+
_ASSERTE(!g_fEEStarted);
512+
513+
hEventFinalizerDone->Wait(INFINITE,FALSE);
514+
hEventFinalizerDone->Reset();
515+
}
516+
494517
// Wait for the finalizer thread to complete one pass.
495518
void FinalizerThread::FinalizerThreadWait()
496519
{

src/coreclr/vm/finalizerthread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ class FinalizerThread
6565
}
6666
}
6767

68+
static void WaitForFinalizerThreadStart();
69+
6870
static void FinalizerThreadWait();
6971

7072
static void SignalFinalizationDone(int observedFullGcCount);

src/coreclr/vm/interoputil.cpp

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,22 +1404,8 @@ VOID EnsureComStarted(BOOL fCoInitCurrentThread)
14041404
}
14051405
CONTRACTL_END;
14061406

1407-
if (g_fComStarted == FALSE)
1408-
{
1409-
FinalizerThread::GetFinalizerThread()->SetRequiresCoInitialize();
1410-
1411-
// Attempt to set the thread's apartment model (to MTA by default). May not
1412-
// succeed (if someone beat us to the punch). That doesn't matter (since
1413-
// CLR objects are now apartment agile), we only care that a CoInitializeEx
1414-
// has been performed on this thread by us.
1415-
if (fCoInitCurrentThread)
1416-
GetThread()->SetApartment(Thread::AS_InMTA);
1417-
1418-
// set the finalizer event
1419-
FinalizerThread::EnableFinalization();
1420-
1421-
g_fComStarted = TRUE;
1422-
}
1407+
// COM is expected to be started on finalizer thread during startup
1408+
_ASSERTE(g_fComStarted);
14231409
}
14241410

14251411
HRESULT EnsureComStartedNoThrow(BOOL fCoInitCurrentThread)
@@ -1436,15 +1422,8 @@ HRESULT EnsureComStartedNoThrow(BOOL fCoInitCurrentThread)
14361422

14371423
HRESULT hr = S_OK;
14381424

1439-
if (!g_fComStarted)
1440-
{
1441-
GCX_COOP();
1442-
EX_TRY
1443-
{
1444-
EnsureComStarted(fCoInitCurrentThread);
1445-
}
1446-
EX_CATCH_HRESULT(hr);
1447-
}
1425+
// COM is expected to be started on finalizer thread during startup
1426+
_ASSERTE(g_fComStarted);
14481427

14491428
return hr;
14501429
}

src/coreclr/vm/threads.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,23 @@ void SetThread(Thread* t)
353353
{
354354
LIMITED_METHOD_CONTRACT
355355

356+
Thread* origThread = gCurrentThreadInfo.m_pThread;
356357
gCurrentThreadInfo.m_pThread = t;
357358
if (t != NULL)
358359
{
360+
_ASSERTE(origThread == NULL);
359361
InitializeCurrentThreadsStaticData(t);
360362
EnsureTlsDestructionMonitor();
361363
t->InitRuntimeThreadLocals();
362364
}
365+
#ifdef TARGET_WINDOWS
366+
else if (origThread != NULL)
367+
{
368+
// Unregister from OS notifications
369+
// This can return false if a thread did not register for OS notification.
370+
OsDetachThread(origThread);
371+
}
372+
#endif
363373

364374
// Clear or set the app domain to the one domain based on if the thread is being nulled out or set
365375
gCurrentThreadInfo.m_pAppDomain = t == NULL ? NULL : AppDomain::GetCurrentDomain();
@@ -865,7 +875,7 @@ void DestroyThread(Thread *th)
865875
// Public function: DetachThread()
866876
// Marks the thread as needing to be destroyed, but doesn't destroy it yet.
867877
//-------------------------------------------------------------------------
868-
HRESULT Thread::DetachThread(BOOL fDLLThreadDetach)
878+
HRESULT Thread::DetachThread(BOOL inTerminationCallback)
869879
{
870880
// !!! Can not use contract here.
871881
// !!! Contract depends on Thread object for GC_TRIGGERS.
@@ -890,9 +900,9 @@ HRESULT Thread::DetachThread(BOOL fDLLThreadDetach)
890900
pErrorInfo->Release();
891901
}
892902

893-
// Revoke our IInitializeSpy registration only if we are not in DLL_THREAD_DETACH
903+
// Revoke our IInitializeSpy registration only if we are not in a thread termination callback
894904
// (COM will do it or may have already done it automatically in that case).
895-
if (!fDLLThreadDetach)
905+
if (!inTerminationCallback)
896906
{
897907
RevokeApartmentSpy();
898908
}

src/coreclr/vm/threads.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ class Thread
678678
};
679679

680680
public:
681-
HRESULT DetachThread(BOOL fDLLThreadDetach);
681+
HRESULT DetachThread(BOOL inTerminationCallback);
682682

683683
void SetThreadState(ThreadState ts)
684684
{

0 commit comments

Comments
 (0)