Skip to content

Commit a95063a

Browse files
committed
Use FLS detach as thread termination notification on windows.
1 parent a5af0ab commit a95063a

File tree

4 files changed

+149
-31
lines changed

4 files changed

+149
-31
lines changed

src/coreclr/nativeaot/Runtime/threadstore.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ void ThreadStore::DetachCurrentThread()
162162
}
163163

164164
// Unregister from OS notifications
165-
// This can return false if detach notification is spurious and does not belong to this thread.
165+
// This can return false if a thread did not register for OS notification.
166166
if (!PalDetachThread(pDetachingThread))
167167
{
168168
return;

src/coreclr/vm/ceemain.cpp

+127-30
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,130 @@ BOOL STDMETHODCALLTYPE EEDllMain( // TRUE on success, FALSE on error.
17021702

17031703
#endif // !defined(CORECLR_EMBEDDED)
17041704

1705+
static void RuntimeThreadShutdown(void* thread)
1706+
{
1707+
Thread* pThread = (Thread*)thread;
1708+
_ASSERTE(pThread == GetThreadNULLOk());
1709+
1710+
if (pThread)
1711+
{
1712+
#ifdef FEATURE_COMINTEROP
1713+
// reset the CoInitialize state
1714+
// so we don't call CoUninitialize during thread detach
1715+
pThread->ResetCoInitialized();
1716+
#endif // FEATURE_COMINTEROP
1717+
// For case where thread calls ExitThread directly, we need to reset the
1718+
// frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode.
1719+
// We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode
1720+
if (pThread->m_pFrame != FRAME_TOP)
1721+
{
1722+
#ifdef _DEBUG
1723+
pThread->m_GCOnTransitionsOK = FALSE;
1724+
#endif
1725+
GCX_COOP_NO_DTOR();
1726+
pThread->m_pFrame = FRAME_TOP;
1727+
GCX_COOP_NO_DTOR_END();
1728+
}
1729+
1730+
pThread->DetachThread(TRUE);
1731+
}
1732+
else
1733+
{
1734+
// Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up
1735+
AssertThreadStaticDataFreed();
1736+
}
1737+
1738+
ThreadDetaching();
1739+
}
1740+
1741+
#ifdef TARGET_WINDOWS
1742+
1743+
// Index for the fiber local storage of the attached thread pointer
1744+
static uint32_t g_flsIndex = FLS_OUT_OF_INDEXES;
1745+
1746+
// This is called when each *fiber* is destroyed. When the home fiber of a thread is destroyed,
1747+
// it means that the thread itself is destroyed.
1748+
// Since we receive that notification outside of the Loader Lock, it allows us to safely acquire
1749+
// the ThreadStore lock in the RuntimeThreadShutdown.
1750+
static void __stdcall FiberDetachCallback(void* lpFlsData)
1751+
{
1752+
ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES);
1753+
ASSERT(lpFlsData == FlsGetValue(g_flsIndex));
1754+
1755+
if (lpFlsData != NULL)
1756+
{
1757+
// The current fiber is the home fiber of a thread, so the thread is shutting down
1758+
RuntimeThreadShutdown(lpFlsData);
1759+
}
1760+
}
1761+
1762+
bool InitFlsSlot()
1763+
{
1764+
// We use fiber detach callbacks to run our thread shutdown code because the fiber detach
1765+
// callback is made without the OS loader lock
1766+
g_flsIndex = FlsAlloc(FiberDetachCallback);
1767+
if (g_flsIndex == FLS_OUT_OF_INDEXES)
1768+
{
1769+
return false;
1770+
}
1771+
1772+
return true;
1773+
}
1774+
1775+
// Register the thread with OS to be notified when thread is about to be destroyed
1776+
// It fails fast if a different thread was already registered with the current fiber.
1777+
// Parameters:
1778+
// thread - thread to attach
1779+
static void OsAttachThread(void* thread)
1780+
{
1781+
void* threadFromCurrentFiber = FlsGetValue(g_flsIndex);
1782+
1783+
if (threadFromCurrentFiber != NULL)
1784+
{
1785+
_ASSERTE(!"Multiple threads encountered from a single fiber");
1786+
COMPlusThrowWin32();
1787+
}
1788+
1789+
// Associate the current fiber with the current thread. This makes the current fiber the thread's "home"
1790+
// fiber. This fiber is the only fiber allowed to execute managed code on this thread. When this fiber
1791+
// is destroyed, we consider the thread to be destroyed.
1792+
FlsSetValue(g_flsIndex, thread);
1793+
}
1794+
1795+
// Detach thread from OS notifications.
1796+
// It fails fast if some other thread value was attached to the current fiber.
1797+
// Parameters:
1798+
// thread - thread to detach
1799+
// Return:
1800+
// true if the thread was detached, false if there was no attached thread
1801+
bool OsDetachThread(void* thread)
1802+
{
1803+
ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES);
1804+
void* threadFromCurrentFiber = FlsGetValue(g_flsIndex);
1805+
1806+
if (threadFromCurrentFiber == NULL)
1807+
{
1808+
// we've seen this thread, but not this fiber. It must be a "foreign" fiber that was
1809+
// borrowing this thread.
1810+
return false;
1811+
}
1812+
1813+
if (threadFromCurrentFiber != thread)
1814+
{
1815+
_ASSERTE(!"Detaching a thread from the wrong fiber");
1816+
COMPlusThrowWin32();
1817+
}
1818+
1819+
FlsSetValue(g_flsIndex, NULL);
1820+
return true;
1821+
}
1822+
1823+
void EnsureTlsDestructionMonitor()
1824+
{
1825+
OsAttachThread(GetThread());
1826+
}
1827+
1828+
#else
17051829
struct TlsDestructionMonitor
17061830
{
17071831
bool m_activated = false;
@@ -1715,36 +1839,7 @@ struct TlsDestructionMonitor
17151839
{
17161840
if (m_activated)
17171841
{
1718-
Thread* thread = GetThreadNULLOk();
1719-
if (thread)
1720-
{
1721-
#ifdef FEATURE_COMINTEROP
1722-
// reset the CoInitialize state
1723-
// so we don't call CoUninitialize during thread detach
1724-
thread->ResetCoInitialized();
1725-
#endif // FEATURE_COMINTEROP
1726-
// For case where thread calls ExitThread directly, we need to reset the
1727-
// frame pointer. Otherwise stackwalk would AV. We need to do it in cooperative mode.
1728-
// We need to set m_GCOnTransitionsOK so this thread won't trigger GC when toggle GC mode
1729-
if (thread->m_pFrame != FRAME_TOP)
1730-
{
1731-
#ifdef _DEBUG
1732-
thread->m_GCOnTransitionsOK = FALSE;
1733-
#endif
1734-
GCX_COOP_NO_DTOR();
1735-
thread->m_pFrame = FRAME_TOP;
1736-
GCX_COOP_NO_DTOR_END();
1737-
}
1738-
1739-
thread->DetachThread(TRUE);
1740-
}
1741-
else
1742-
{
1743-
// Since we don't actually cleanup the TLS data along this path, verify that it is already cleaned up
1744-
AssertThreadStaticDataFreed();
1745-
}
1746-
1747-
ThreadDetaching();
1842+
RuntimeThreadShutdown(GetThreadNULLOk());
17481843
}
17491844
}
17501845
};
@@ -1758,6 +1853,8 @@ void EnsureTlsDestructionMonitor()
17581853
tls_destructionMonitor.Activate();
17591854
}
17601855

1856+
#endif
1857+
17611858
#ifdef DEBUGGING_SUPPORTED
17621859
//
17631860
// InitializeDebugger initialized the Runtime-side COM+ Debugging Services

src/coreclr/vm/ceemain.h

+4
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+
bool InitFlsSlot();
51+
bool OsDetachThread(void* thread);
52+
#endif
4953

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

src/coreclr/vm/threads.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,22 @@ 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
{
359360
InitializeCurrentThreadsStaticData(t);
360361
EnsureTlsDestructionMonitor();
361362
t->InitRuntimeThreadLocals();
362363
}
364+
#ifdef TARGET_WINDOWS
365+
else if (origThread != NULL)
366+
{
367+
// Unregister from OS notifications
368+
// This can return false if a thread did not register for OS notification.
369+
OsDetachThread(origThread);
370+
}
371+
#endif
363372

364373
// Clear or set the app domain to the one domain based on if the thread is being nulled out or set
365374
gCurrentThreadInfo.m_pAppDomain = t == NULL ? NULL : AppDomain::GetCurrentDomain();
@@ -1039,6 +1048,14 @@ void InitThreadManager()
10391048
}
10401049
CONTRACTL_END;
10411050

1051+
#ifdef TARGET_WINDOWS
1052+
if (!InitFlsSlot())
1053+
{
1054+
_ASSERTE(!"Initialization of a FLS slot failed.");
1055+
COMPlusThrowWin32();
1056+
}
1057+
#endif
1058+
10421059
// All patched helpers should fit into one page.
10431060
// If you hit this assert on retail build, there is most likely problem with BBT script.
10441061
_ASSERTE_ALL_BUILDS((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart > (ptrdiff_t)0);

0 commit comments

Comments
 (0)