Skip to content

Commit 69d8076

Browse files
VSadovjkotas
andauthored
Use FLS detach as thread termination notification on windows. (#110589)
* 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 --------- Co-authored-by: Jan Kotas <[email protected]>
1 parent 9bededd commit 69d8076

File tree

4 files changed

+140
-31
lines changed

4 files changed

+140
-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

+122-30
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,125 @@ BOOL STDMETHODCALLTYPE EEDllMain( // TRUE on success, FALSE on error.
17011701

17021702
#endif // !defined(CORECLR_EMBEDDED)
17031703

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

1850+
#endif
1851+
17601852
#ifdef DEBUGGING_SUPPORTED
17611853
//
17621854
// 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+
void InitFlsSlot();
51+
void OsDetachThread(void* thread);
52+
#endif
4953

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

src/coreclr/vm/threads.cpp

+13
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,10 @@ void InitThreadManager()
10391048
}
10401049
CONTRACTL_END;
10411050

1051+
#ifdef TARGET_WINDOWS
1052+
InitFlsSlot();
1053+
#endif
1054+
10421055
// All patched helpers should fit into one page.
10431056
// If you hit this assert on retail build, there is most likely problem with BBT script.
10441057
_ASSERTE_ALL_BUILDS((BYTE*)JIT_PatchedCodeLast - (BYTE*)JIT_PatchedCodeStart > (ptrdiff_t)0);

0 commit comments

Comments
 (0)