Skip to content

Commit 92e8eab

Browse files
VSadovjkotas
andauthored
[NativeAOT] Cleanup and rationalizing process/thread termination scenario (#80063)
* remove m_pTEB * set g_threadPerformingShutdown in DllMain * more thread detach cleanup * rationalizing DetachCurrentThread * OnProcessExit * Remove g_SuspendEELock * fixes * comment tweak * fix assert (thread could be detached before ending up performing shutdown) * Apply suggestions from code review Co-authored-by: Jan Kotas <[email protected]> * remove unused RtuDllMain Co-authored-by: Jan Kotas <[email protected]>
1 parent c71ad8c commit 92e8eab

File tree

13 files changed

+94
-197
lines changed

13 files changed

+94
-197
lines changed

src/coreclr/nativeaot/Bootstrap/main.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ static char& __unbox_z = __stop___unbox;
9494
#endif // _MSC_VER
9595

9696
extern "C" bool RhInitialize();
97-
extern "C" void RhpShutdown();
9897
extern "C" void RhSetRuntimeInitializationCallback(int (*fPtr)());
9998

10099
extern "C" bool RhRegisterOSModule(void * pModule,
@@ -213,11 +212,7 @@ int main(int argc, char* argv[])
213212
if (initval != 0)
214213
return initval;
215214

216-
int retval = __managed__Main(argc, argv);
217-
218-
RhpShutdown();
219-
220-
return retval;
215+
return __managed__Main(argc, argv);
221216
}
222217
#endif // !NATIVEAOT_DLL
223218

src/coreclr/nativeaot/Runtime/Crst.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// functionality (in particular there is no rank violation checking).
99
//
1010

11+
#ifndef __Crst_h__
12+
#define __Crst_h__
13+
1114
enum CrstType
1215
{
1316
CrstHandleTable,
@@ -20,7 +23,7 @@ enum CrstType
2023
CrstRestrictedCallouts,
2124
CrstObjectiveCMarshalCallouts,
2225
CrstGcStressControl,
23-
CrstSuspendEE,
26+
CrstThreadStore,
2427
CrstCastCache,
2528
CrstYieldProcessorNormalized,
2629
};
@@ -126,3 +129,5 @@ class CrstHolderWithState
126129
return m_pLock;
127130
}
128131
};
132+
133+
#endif //__Crst_h__

src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@ GPTR_DECL(Thread, g_pFinalizerThread);
2929
CLREventStatic g_FinalizerEvent;
3030
CLREventStatic g_FinalizerDoneEvent;
3131

32-
// Finalizer method implemented by redhawkm.
3332
extern "C" void __cdecl ProcessFinalizers();
3433

3534
// Unmanaged front-end to the finalizer thread. We require this because at the point the GC creates the
36-
// finalizer thread we're still executing the DllMain for RedhawkU. At that point we can't run managed code
37-
// successfully (in particular module initialization code has not run for RedhawkM). Instead this method waits
35+
// finalizer thread we can't run managed code. Instead this method waits
3836
// for the first finalization request (by which time everything must be up and running) and kicks off the
39-
// managed portion of the thread at that point.
37+
// managed portion of the thread at that point
4038
uint32_t WINAPI FinalizerStart(void* pContext)
4139
{
4240
HANDLE hFinalizerEvent = (HANDLE)pContext;
@@ -62,8 +60,7 @@ uint32_t WINAPI FinalizerStart(void* pContext)
6260
UInt32_BOOL fResult = PalSetEvent(hFinalizerEvent);
6361
ASSERT(fResult);
6462

65-
// Run the managed portion of the finalizer. Until we implement (non-process) shutdown this call will
66-
// never return.
63+
// Run the managed portion of the finalizer. This call will never return.
6764

6865
ProcessFinalizers();
6966

src/coreclr/nativeaot/Runtime/gcrhenv.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ uint32_t EtwCallback(uint32_t IsEnabled, RH_ETW_CONTEXT * pContext)
144144
// success or false if a subsystem failed to initialize.
145145

146146
#ifndef DACCESS_COMPILE
147-
CrstStatic g_SuspendEELock;
148147
#ifdef _MSC_VER
149148
#pragma warning(disable:4815) // zero-sized array in stack object will have no elements
150149
#endif // _MSC_VER
@@ -169,9 +168,6 @@ bool RedhawkGCInterface::InitializeSubsystems()
169168
g_FreeObjectEEType.InitializeAsGcFreeType();
170169
g_pFreeObjectEEType = &g_FreeObjectEEType;
171170

172-
if (!g_SuspendEELock.InitNoThrow(CrstSuspendEE))
173-
return false;
174-
175171
#ifdef FEATURE_SVR_GC
176172
g_heap_type = (g_pRhConfig->GetgcServer() && PalGetProcessCpuCount() > 1) ? GC_HEAP_SVR : GC_HEAP_WKS;
177173
#else
@@ -642,10 +638,8 @@ void GCToEEInterface::SuspendEE(SUSPEND_REASON reason)
642638

643639
FireEtwGCSuspendEEBegin_V1(Info.SuspendEE.Reason, Info.SuspendEE.GcCount, GetClrInstanceId());
644640

645-
g_SuspendEELock.Enter();
646-
641+
GetThreadStore()->LockThreadStore();
647642
GCHeapUtilities::GetGCHeap()->SetGCInProgress(TRUE);
648-
649643
GetThreadStore()->SuspendAllThreads(true);
650644

651645
FireEtwGCSuspendEEEnd_V1(GetClrInstanceId());
@@ -669,8 +663,7 @@ void GCToEEInterface::RestartEE(bool /*bFinishedGC*/)
669663

670664
GetThreadStore()->ResumeAllThreads(true);
671665
GCHeapUtilities::GetGCHeap()->SetGCInProgress(FALSE);
672-
673-
g_SuspendEELock.Leave();
666+
GetThreadStore()->UnlockThreadStore();
674667

675668
FireEtwGCRestartEEEnd_V1(GetClrInstanceId());
676669
}

src/coreclr/nativeaot/Runtime/startup.cpp

Lines changed: 31 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -443,25 +443,25 @@ static void UninitDLL()
443443
#endif // PROFILE_STARTUP
444444
}
445445

446-
volatile Thread* g_threadPerformingShutdown = NULL;
447-
448-
static void DllThreadDetach()
446+
#ifdef _WIN32
447+
// This is set to the thread that initiates and performs the shutdown and may run
448+
// after other threads are rudely terminated. So far this is a Windows-specific concern.
449+
//
450+
// On POSIX OSes a process typically lives as long as any of its threads are alive or until
451+
// the process is terminated via `exit()` or a signal. Thus there is no such distinction
452+
// between threads.
453+
Thread* g_threadPerformingShutdown = NULL;
454+
455+
static void __cdecl OnProcessExit()
449456
{
450-
// BEWARE: loader lock is held here!
451-
452-
// Should have already received a call to FiberDetach for this thread's "home" fiber.
453-
Thread* pCurrentThread = ThreadStore::GetCurrentThreadIfAvailable();
454-
if (pCurrentThread != NULL && !pCurrentThread->IsDetached())
455-
{
456-
// Once shutdown starts, RuntimeThreadShutdown callbacks are ignored, implying that
457-
// it is no longer guaranteed that exiting threads will be detached.
458-
if (g_threadPerformingShutdown != NULL)
459-
{
460-
ASSERT_UNCONDITIONALLY("Detaching thread whose home fiber has not been detached");
461-
RhFailFast();
462-
}
463-
}
457+
// The process is exiting and the current thread is performing the shutdown.
458+
// When this thread exits some threads may be already rudely terminated.
459+
// It would not be a good idea for this thread to wait on any locks
460+
// or run managed code at shutdown, so we will not try detaching it.
461+
Thread* currentThread = ThreadStore::RawGetCurrentThread();
462+
g_threadPerformingShutdown = currentThread;
464463
}
464+
#endif
465465

466466
void RuntimeThreadShutdown(void* thread)
467467
{
@@ -470,35 +470,38 @@ void RuntimeThreadShutdown(void* thread)
470470
// that is made for the single thread that runs the final stages of orderly process
471471
// shutdown (i.e., the thread that delivers the DLL_PROCESS_DETACH notifications when the
472472
// process is being torn down via an ExitProcess call).
473+
// In such case we do not detach.
473474

474-
UNREFERENCED_PARAMETER(thread);
475+
#ifdef _WIN32
476+
ASSERT((Thread*)thread == ThreadStore::GetCurrentThread());
475477

476-
#ifdef TARGET_UNIX
477-
// Some Linux toolset versions call thread-local destructors during shutdown on a wrong thread.
478-
if ((Thread*)thread != ThreadStore::GetCurrentThread())
478+
// Do not try detaching the thread that performs the shutdown.
479+
if (g_threadPerformingShutdown == thread)
479480
{
481+
// At this point other threads could be terminated rudely while leaving runtime
482+
// in inconsistent state, so we would be risking blocking the process from exiting.
480483
return;
481484
}
482485
#else
483-
ASSERT((Thread*)thread == ThreadStore::GetCurrentThread());
484-
485-
// Do not do shutdown for the thread that performs the shutdown.
486-
// other threads could be terminated before it and could leave TLS locked
487-
if ((Thread*)thread == g_threadPerformingShutdown)
486+
// Some Linux toolset versions call thread-local destructors during shutdown on a wrong thread.
487+
if ((Thread*)thread != ThreadStore::GetCurrentThread())
488488
{
489489
return;
490490
}
491-
492491
#endif
493492

494-
ThreadStore::DetachCurrentThread(g_threadPerformingShutdown != NULL);
493+
ThreadStore::DetachCurrentThread();
495494
}
496495

497496
extern "C" bool RhInitialize()
498497
{
499498
if (!PalInit())
500499
return false;
501500

501+
#ifdef _WIN32
502+
atexit(&OnProcessExit);
503+
#endif
504+
502505
if (!InitDLL(PalGetModuleHandleFromPointer((void*)&RhInitialize)))
503506
return false;
504507

@@ -508,47 +511,4 @@ extern "C" bool RhInitialize()
508511
return true;
509512
}
510513

511-
//
512-
// Currently called only from a managed executable once Main returns, this routine does whatever is needed to
513-
// cleanup managed state before exiting. There's not a lot here at the moment since we're always about to let
514-
// the OS tear the process down anyway.
515-
//
516-
// @TODO: Eventually we'll probably have a hosting API and explicit shutdown request. When that happens we'll
517-
// something more sophisticated here since we won't be able to rely on the OS cleaning up after us.
518-
//
519-
COOP_PINVOKE_HELPER(void, RhpShutdown, ())
520-
{
521-
// Indicate that runtime shutdown is complete and that the caller is about to start shutting down the entire process.
522-
g_threadPerformingShutdown = ThreadStore::RawGetCurrentThread();
523-
}
524-
525-
#ifdef _WIN32
526-
EXTERN_C UInt32_BOOL WINAPI RtuDllMain(HANDLE hPalInstance, uint32_t dwReason, void* pvReserved)
527-
{
528-
switch (dwReason)
529-
{
530-
case DLL_PROCESS_ATTACH:
531-
{
532-
STARTUP_TIMELINE_EVENT(PROCESS_ATTACH_BEGIN);
533-
534-
if (!InitDLL(hPalInstance))
535-
return FALSE;
536-
537-
STARTUP_TIMELINE_EVENT(PROCESS_ATTACH_COMPLETE);
538-
}
539-
break;
540-
541-
case DLL_PROCESS_DETACH:
542-
UninitDLL();
543-
break;
544-
545-
case DLL_THREAD_DETACH:
546-
DllThreadDetach();
547-
break;
548-
}
549-
550-
return TRUE;
551-
}
552-
#endif // _WIN32
553-
554514
#endif // !DACCESS_COMPILE

src/coreclr/nativeaot/Runtime/thread.cpp

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,6 @@ void Thread::Construct()
278278
if (!PalGetMaximumStackBounds(&m_pStackLow, &m_pStackHigh))
279279
RhFailFast();
280280

281-
m_pTEB = PalNtCurrentTeb();
282-
283281
#ifdef STRESS_LOG
284282
if (StressLog::StressLogOn(~0u, 0))
285283
m_pThreadStressLog = StressLog::CreateThreadStressLog(this);
@@ -341,12 +339,8 @@ bool Thread::IsCurrentThread()
341339

342340
void Thread::Detach()
343341
{
344-
// Thread::Destroy is called when the thread's "home" fiber dies. We mark the thread as "detached" here
345-
// so that we can validate, in our DLL_THREAD_DETACH handler, that the thread was already destroyed at that
346-
// point.
347-
SetDetached();
348-
349342
RedhawkGCInterface::ReleaseAllocContext(GetAllocContext());
343+
SetDetached();
350344
}
351345

352346
void Thread::Destroy()
@@ -1099,20 +1093,6 @@ void Thread::ValidateExInfoStack()
10991093
#endif // !DACCESS_COMPILE
11001094
}
11011095

1102-
1103-
1104-
// Retrieve the start of the TLS storage block allocated for the given thread for a specific module identified
1105-
// by the TLS slot index allocated to that module and the offset into the OS allocated block at which
1106-
// Redhawk-specific data is stored.
1107-
PTR_UInt8 Thread::GetThreadLocalStorage(uint32_t uTlsIndex, uint32_t uTlsStartOffset)
1108-
{
1109-
#if 0
1110-
return (*(uint8_t***)(m_pTEB + OFFSETOF__TEB__ThreadLocalStoragePointer))[uTlsIndex] + uTlsStartOffset;
1111-
#else
1112-
return (*dac_cast<PTR_PTR_PTR_UInt8>(dac_cast<TADDR>(m_pTEB) + OFFSETOF__TEB__ThreadLocalStoragePointer))[uTlsIndex] + uTlsStartOffset;
1113-
#endif
1114-
}
1115-
11161096
#ifndef DACCESS_COMPILE
11171097

11181098
#ifndef TARGET_UNIX

src/coreclr/nativeaot/Runtime/thread.h

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,11 @@ class Thread;
3737
// can be retrieved via GetInterruptedContext()
3838
#define INTERRUPTED_THREAD_MARKER ((PInvokeTransitionFrame*)(ptrdiff_t)-2)
3939

40-
enum SyncRequestResult
41-
{
42-
TryAgain,
43-
SuccessUnmanaged,
44-
SuccessManaged,
45-
};
46-
4740
typedef DPTR(PAL_LIMITED_CONTEXT) PTR_PAL_LIMITED_CONTEXT;
4841

4942
struct ExInfo;
5043
typedef DPTR(ExInfo) PTR_ExInfo;
5144

52-
5345
// Also defined in ExceptionHandling.cs, layouts must match.
5446
// When adding new fields to this struct, ensure they get properly initialized in the exception handling
5547
// assembly stubs
@@ -94,7 +86,6 @@ struct ThreadBuffer
9486
GCFrameRegistration* m_pGCFrameRegistrations;
9587
PTR_VOID m_pStackLow;
9688
PTR_VOID m_pStackHigh;
97-
PTR_UInt8 m_pTEB; // Pointer to OS TEB structure for this thread
9889
EEThreadId m_threadId; // OS thread ID
9990
PTR_VOID m_pThreadStressLog; // pointer to head of thread's StressLogChunks
10091
NATIVE_CONTEXT* m_interruptedContext; // context for an asynchronously interrupted thread.
@@ -105,7 +96,6 @@ struct ThreadBuffer
10596
#ifdef FEATURE_GC_STRESS
10697
uint32_t m_uRand; // current per-thread random number
10798
#endif // FEATURE_GC_STRESS
108-
10999
};
110100

111101
struct ReversePInvokeFrame
@@ -182,13 +172,16 @@ class Thread : private ThreadBuffer
182172
void GcScanRootsWorker(void * pfnEnumCallback, void * pvCallbackData, StackFrameIterator & sfIter);
183173

184174
public:
185-
186-
void Detach(); // First phase of thread destructor, executed with thread store lock taken
187-
void Destroy(); // Second phase of thread destructor, executed without thread store lock taken
175+
// First phase of thread destructor, disposes stuff related to GC.
176+
// Executed with thread store lock taken so GC cannot happen.
177+
void Detach();
178+
// Second phase of thread destructor.
179+
// Executed without thread store lock taken.
180+
void Destroy();
188181

189182
bool IsInitialized();
190183

191-
gc_alloc_context * GetAllocContext(); // @TODO: I would prefer to not expose this in this way
184+
gc_alloc_context * GetAllocContext();
192185

193186
#ifndef DACCESS_COMPILE
194187
uint64_t GetPalThreadIdForLogging();
@@ -212,11 +205,7 @@ class Thread : private ThreadBuffer
212205
void SetSuppressGcStress();
213206
void ClearSuppressGcStress();
214207
bool IsWithinStackBounds(PTR_VOID p);
215-
216208
void GetStackBounds(PTR_VOID * ppStackLow, PTR_VOID * ppStackHigh);
217-
218-
PTR_UInt8 GetThreadLocalStorage(uint32_t uTlsIndex, uint32_t uTlsStartOffset);
219-
220209
void PushExInfo(ExInfo * pExInfo);
221210
void ValidateExInfoPop(ExInfo * pExInfo, void * limitSP);
222211
void ValidateExInfoStack();

0 commit comments

Comments
 (0)