@@ -6989,6 +6989,9 @@ namespace
69896989 }
69906990 CONTRACTL_END;
69916991
6992+ // If there's no threads to detach right now,
6993+ // go to sleep.
6994+ if (Thread::m_DetachCount == 0 )
69926995 {
69936996 GCX_PREEMP ();
69946997
@@ -7024,44 +7027,83 @@ namespace
70247027 }
70257028}
70267029
7027- void
7030+ BOOL
70287031ThreadCleanupThread::EnsureCleanupThreadExists ()
70297032{
70307033 CONTRACTL{
70317034 THROWS;
70327035 GC_TRIGGERS;
7033- MODE_ANY ;
7036+ MODE_PREEMPTIVE ;
70347037 } CONTRACTL_END;
70357038
7036- ForeignThreadsToCleanUpEvent = new CLREvent ();
7037- ForeignThreadsToCleanUpEvent->CreateAutoEvent (FALSE );
7039+ if (ForeignThreadsToCleanUpEvent != nullptr )
7040+ {
7041+ // Another thread created the event first.
7042+ // Set the event in case another thread just signaled the finalizer
7043+ // right before we were called.
7044+ ForeignThreadsToCleanUpEvent->Set ();
7045+ return TRUE ;
7046+ }
7047+
7048+ NewHolder<CLREvent> tempEvent = new CLREvent ();
7049+ // We may have threads waiting to clean up already, so create the event in the signaled state.
7050+ tempEvent->CreateAutoEvent (TRUE );
7051+
7052+ if (InterlockedCompareExchangeT (&ForeignThreadsToCleanUpEvent, tempEvent.GetValue (), nullptr ) != nullptr )
7053+ {
7054+ // Another thread created the event first, just return.
7055+ return TRUE ;
7056+ }
7057+
7058+ tempEvent.SuppressRelease ();
70387059
7039- Thread* pCleanupThread = SetupUnstartedThread ();
7060+ Holder< Thread*,DoNothing<Thread*>,DeleteThread> pCleanupThread ( SetupUnstartedThread () );
70407061
7041- if (pCleanupThread->CreateNewThread (0 , &ForeignThreadCleanupThreadStart, pCleanupThread, W (" .NET External Thread Cleanup Thread" )))
7062+ // Don't block process shutdown on the cleanup thread.
7063+ pCleanupThread.GetValue ()->SetBackground (TRUE );
7064+
7065+ if (pCleanupThread.GetValue ()->CreateNewThread (0 , &ForeignThreadCleanupThreadStart, pCleanupThread.GetValue (), W (" .NET Detached Cleanup Thread" )))
70427066 {
7043- DWORD dwRet = pCleanupThread->StartThread ();
7067+ DWORD dwRet = pCleanupThread. GetValue () ->StartThread ();
70447068
70457069 // When running under a user mode native debugger there is a race
70467070 // between the moment we've created the thread (in CreateNewThread) and
70477071 // the moment we resume it (in StartThread); the debugger may receive
70487072 // the "ct" (create thread) notification, and it will attempt to
70497073 // suspend/resume all threads in the process. Now imagine the debugger
70507074 // resumes this thread first, and only later does it try to resume the
7051- // newly created thread (the finalizer thread) . In these conditions our
7075+ // newly created thread. In these conditions our
70527076 // call to ResumeThread may come before the debugger's call to ResumeThread
70537077 // actually causing dwRet to equal 2.
70547078 // We cannot use IsDebuggerPresent() in the condition below because the
70557079 // debugger may have been detached between the time it got the notification
70567080 // and the moment we execute the test below.
70577081 _ASSERTE (dwRet == 1 || dwRet == 2 );
7082+ pCleanupThread.SuppressRelease ();
7083+ return TRUE ;
70587084 }
7085+
7086+ return FALSE ;
70597087}
70607088void
70617089ThreadCleanupThread::SetHasThreadsToCleanUp ()
70627090{
7063- _ASSERT (ForeignThreadsToCleanUpEvent != nullptr );
7064- ForeignThreadsToCleanUpEvent->Set ();
7091+ // If we don't have a dedicated cleanup thread,
7092+ // we will cleanup detached threads on the finalizer thread.
7093+ if (ForeignThreadsToCleanUpEvent == nullptr )
7094+ {
7095+ FinalizerThread::EnableFinalization ();
7096+ }
7097+ else
7098+ {
7099+ ForeignThreadsToCleanUpEvent->Set ();
7100+ }
7101+ }
7102+
7103+ bool
7104+ ThreadCleanupThread::UsingExternalCleanupThread ()
7105+ {
7106+ return ForeignThreadsToCleanUpEvent != nullptr ;
70657107}
70667108#endif // DACCESS_COMPILE
70677109
0 commit comments