result |= USER_WAIT_SLEEP_JOIN;
}
- // Don't report a SuspendRequested if the thread has actually Suspended.
- if ((ts & Thread::TS_UserSuspendPending) && (ts & Thread::TS_SyncSuspended))
- {
- result |= USER_SUSPENDED;
- }
- else if (ts & Thread::TS_UserSuspendPending)
- {
- result |= USER_SUSPEND_REQUESTED;
- }
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(ts & Thread::TS_UserSuspendPending));
if (pThread->IsThreadPoolThread())
{
CONFIG_DWORD_INFO(INTERNAL_SuspendThreadDeadlockTimeoutMs, W("SuspendThreadDeadlockTimeoutMs"), 2000, "")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_ThreadSuspendInjection, W("INTERNAL_ThreadSuspendInjection"), 1, "Specifies whether to inject activations for thread suspension on Unix")
+//
+// Thread (miscellaneous)
+//
+RETAIL_CONFIG_DWORD_INFO(INTERNAL_Thread_DeadThreadCountThresholdForGCTrigger, W("Thread_DeadThreadCountThresholdForGCTrigger"), 75, "In the heuristics to clean up dead threads, this threshold must be reached before triggering a GC will be considered. Set to 0 to disable triggering a GC based on dead threads.")
+RETAIL_CONFIG_DWORD_INFO(INTERNAL_Thread_DeadThreadGCTriggerPeriodMilliseconds, W("Thread_DeadThreadGCTriggerPeriodMilliseconds"), 1000 * 60 * 30, "In the heuristics to clean up dead threads, this much time must have elapsed since the previous max-generation GC before triggering another GC will be considered")
+
//
// Threadpool
//
if (state & Thread::TS_Interruptible)
res |= ThreadWaitSleepJoin;
- // Don't report a SuspendRequested if the thread has actually Suspended.
- if ((state & Thread::TS_UserSuspendPending) &&
- (state & Thread::TS_SyncSuspended)
- )
- {
- res |= ThreadSuspended;
- }
- else
- if (state & Thread::TS_UserSuspendPending)
- {
- res |= ThreadSuspendRequested;
- }
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(state & Thread::TS_UserSuspendPending));
HELPER_METHOD_POLL();
HELPER_METHOD_FRAME_END();
goto LSafeToTrace;
}
- if (state & Thread::TS_UserSuspendPending)
- {
- if (state & Thread::TS_SyncSuspended)
- {
- goto LSafeToTrace;
- }
-
-#ifndef DISABLE_THREADSUSPEND
- // On Mac don't perform the optimization below, but rather wait for
- // the suspendee to set the TS_SyncSuspended flag
-
- // The target thread is not actually suspended yet, but if it is
- // in preemptive mode, then it is still safe to trace. Before we
- // can look at another thread's GC mode, we have to suspend it:
- // The target thread updates its GC mode flag with non-interlocked
- // operations, and Thread::SuspendThread drains the CPU's store
- // buffer (by virtue of calling GetThreadContext).
- switch (pThread->SuspendThread())
- {
- case Thread::STR_Success:
- if (!pThread->PreemptiveGCDisabledOther())
- {
- pThread->ResumeThread();
- goto LSafeToTrace;
- }
-
- // Refuse to trace the stack.
- //
- // Note that there is a pretty large window in-between when the
- // target thread sets the GC mode to cooperative, and when it
- // actually sets the TS_SyncSuspended bit. In this window, we
- // will refuse to take a stack trace even though it would be
- // safe to do so.
- pThread->ResumeThread();
- break;
- case Thread::STR_Failure:
- case Thread::STR_NoStressLog:
- break;
- case Thread::STR_UnstartedOrDead:
- // We know the thread is not unstarted, because we checked for
- // TS_Unstarted above.
- _ASSERTE(!(state & Thread::TS_Unstarted));
-
- // Since the thread is dead, it is safe to trace.
- goto LSafeToTrace;
- case Thread::STR_SwitchedOut:
- if (!pThread->PreemptiveGCDisabledOther())
- {
- goto LSafeToTrace;
- }
- break;
- default:
- UNREACHABLE();
- }
-#endif // DISABLE_THREADSUSPEND
- }
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(state & Thread::TS_UserSuspendPending));
COMPlusThrow(kThreadStateException, IDS_EE_THREAD_BAD_STATE);
ret |= (unsigned)USER_WAIT_SLEEP_JOIN;
}
- // Don't report a SuspendRequested if the thread has actually Suspended.
- if ( ((ts & Thread::TS_UserSuspendPending) && (ts & Thread::TS_SyncSuspended)))
- {
- ret |= (unsigned)USER_SUSPENDED;
- }
- else if (ts & Thread::TS_UserSuspendPending)
- {
- ret |= (unsigned)USER_SUSPEND_REQUESTED;
- }
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(ts & Thread::TS_UserSuspendPending));
LOG((LF_CORDB,LL_INFO1000, "EEDbgII::GUS: thread 0x%x (id:0x%x)"
" userThreadState is 0x%x\n", pThread, pThread->GetThreadId(), ret));
}
ULONGLONG dwCurTickCount = CLRGetTickCount64();
if (pThread && pThread->m_State & (Thread::TS_UserSuspendPending | Thread::TS_DebugSuspendPending)) {
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(pThread->m_State & Thread::TS_UserSuspendPending));
dwBeginTickCount = dwCurTickCount;
}
if (dwCurTickCount - dwBeginTickCount >= maxTotalWait)
//
RCWWalker::OnGCStarted(condemned);
#endif // FEATURE_COMINTEROP
+
+ if (condemned == max_gen)
+ {
+ ThreadStore::s_pThreadStore->OnMaxGenerationGCStarted();
+ }
}
void GCToEEInterface::GcDone(int condemned)
#ifdef FEATURE_COMINTEROP
m_fDisableComObjectEagerCleanup = false;
#endif //FEATURE_COMINTEROP
+ m_fHasDeadThreadBeenConsideredForGCTrigger = false;
m_Context = NULL;
m_TraceCallCount = 0;
m_ThrewControlForThread = 0;
{
WRAPPER_NO_CONTRACT;
- _ASSERTE(!m_SafeEvent.IsValid());
- _ASSERTE(!m_UserSuspendEvent.IsValid());
_ASSERTE(!m_DebugSuspendEvent.IsValid());
_ASSERTE(!m_EventWait.IsValid());
BOOL fOK = TRUE;
EX_TRY {
// create a manual reset event for getting the thread to a safe point
- m_SafeEvent.CreateManualEvent(FALSE);
- m_UserSuspendEvent.CreateManualEvent(FALSE);
m_DebugSuspendEvent.CreateManualEvent(FALSE);
m_EventWait.CreateManualEvent(TRUE);
}
EX_CATCH {
fOK = FALSE;
- if (!m_SafeEvent.IsValid()) {
- m_SafeEvent.CloseEvent();
- }
-
- if (!m_UserSuspendEvent.IsValid()) {
- m_UserSuspendEvent.CloseEvent();
- }
if (!m_DebugSuspendEvent.IsValid()) {
m_DebugSuspendEvent.CloseEvent();
}
#endif // PROFILING_SUPPORTED
- // Is there a pending user suspension?
- if (m_State & TS_SuspendUnstarted)
- {
- BOOL doSuspend = FALSE;
-
- {
- ThreadStoreLockHolder TSLockHolder;
-
- // Perhaps we got resumed before it took effect?
- if (m_State & TS_SuspendUnstarted)
- {
- FastInterlockAnd((ULONG *) &m_State, ~TS_SuspendUnstarted);
- SetupForSuspension(TS_UserSuspendPending);
- MarkForSuspension(TS_UserSuspendPending);
- doSuspend = TRUE;
- }
- }
-
- if (doSuspend)
- {
- GCX_PREEMP();
- WaitSuspendEvents();
- }
- }
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_SuspendUnstarted));
}
return res;
CloseHandle(GetThreadHandle());
}
- if (m_SafeEvent.IsValid())
- {
- m_SafeEvent.CloseEvent();
- }
- if (m_UserSuspendEvent.IsValid())
- {
- m_UserSuspendEvent.CloseEvent();
- }
if (m_DebugSuspendEvent.IsValid())
{
m_DebugSuspendEvent.CloseEvent();
FastInterlockOr((ULONG *) &m_State, TS_Dead);
ThreadStore::s_pThreadStore->m_DeadThreadCount++;
+ ThreadStore::s_pThreadStore->IncrementDeadThreadCountForGCTrigger();
if (IsUnstarted())
ThreadStore::s_pThreadStore->m_UnstartedThreadCount--;
if (m_State & TS_DebugSuspendPending)
UnmarkForSuspension(~TS_DebugSuspendPending);
- if (m_State & TS_UserSuspendPending)
- UnmarkForSuspension(~TS_UserSuspendPending);
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_UserSuspendPending));
if (CurrentThreadID == ThisThreadID && IsAbortRequested())
{
m_BackgroundThreadCount(0),
m_PendingThreadCount(0),
m_DeadThreadCount(0),
+ m_DeadThreadCountForGCTrigger(0),
+ m_TriggerGCForDeadThreads(false),
m_GuidCreated(FALSE),
m_HoldingThread(0)
{
s_pWaitForStackCrawlEvent = new CLREvent();
s_pWaitForStackCrawlEvent->CreateManualEvent(FALSE);
+
+ s_DeadThreadCountThresholdForGCTrigger =
+ static_cast<LONG>(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Thread_DeadThreadCountThresholdForGCTrigger));
+ if (s_DeadThreadCountThresholdForGCTrigger < 0)
+ {
+ s_DeadThreadCountThresholdForGCTrigger = 0;
+ }
+ s_DeadThreadGCTriggerPeriodMilliseconds =
+ CLRConfig::GetConfigValue(CLRConfig::INTERNAL_Thread_DeadThreadGCTriggerPeriodMilliseconds);
}
// Enter and leave the critical section around the thread store. Clients should
s_pThreadStore->m_ThreadCount--;
if (target->IsDead())
+ {
s_pThreadStore->m_DeadThreadCount--;
+ s_pThreadStore->DecrementDeadThreadCountForGCTrigger();
+ }
// Unstarted threads are not in the Background count:
if (target->IsUnstarted())
CheckForEEShutdown();
}
+LONG ThreadStore::s_DeadThreadCountThresholdForGCTrigger = 0;
+DWORD ThreadStore::s_DeadThreadGCTriggerPeriodMilliseconds = 0;
+
+void ThreadStore::IncrementDeadThreadCountForGCTrigger()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Although all increments and decrements are usually done inside a lock, that is not sufficient to synchronize with a
+ // background GC thread resetting this value, hence the interlocked operation. Ignore overflow; overflow would likely never
+ // occur, the count is treated as unsigned, and nothing bad would happen if it were to overflow.
+ SIZE_T count = static_cast<SIZE_T>(FastInterlockIncrement(&m_DeadThreadCountForGCTrigger));
+
+ SIZE_T countThreshold = static_cast<SIZE_T>(s_DeadThreadCountThresholdForGCTrigger);
+ if (count < countThreshold || countThreshold == 0)
+ {
+ return;
+ }
+
+ IGCHeap *gcHeap = GCHeapUtilities::GetGCHeap();
+ if (gcHeap == nullptr)
+ {
+ return;
+ }
+
+ SIZE_T gcLastMilliseconds = gcHeap->GetLastGCStartTime(max_generation);
+ SIZE_T gcNowMilliseconds = gcHeap->GetNow();
+ if (gcNowMilliseconds - gcLastMilliseconds < s_DeadThreadGCTriggerPeriodMilliseconds)
+ {
+ return;
+ }
+
+ if (!g_fEEStarted) // required for FinalizerThread::EnableFinalization() below
+ {
+ return;
+ }
+
+ // The GC is triggered on the finalizer thread since it's not safe to trigger it on DLL_THREAD_DETACH.
+ // TriggerGCForDeadThreadsIfNecessary() will determine which generation of GC to trigger, and may not actually trigger a GC.
+ // If a GC is triggered, since there would be a delay before the dead thread count is updated, clear the count and wait for
+ // it to reach the threshold again. If a GC would not be triggered, the count is still cleared here to prevent waking up the
+ // finalizer thread to do the work in TriggerGCForDeadThreadsIfNecessary() for every dead thread.
+ m_DeadThreadCountForGCTrigger = 0;
+ m_TriggerGCForDeadThreads = true;
+ FinalizerThread::EnableFinalization();
+}
+
+void ThreadStore::DecrementDeadThreadCountForGCTrigger()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Although all increments and decrements are usually done inside a lock, that is not sufficient to synchronize with a
+ // background GC thread resetting this value, hence the interlocked operation.
+ if (FastInterlockDecrement(&m_DeadThreadCountForGCTrigger) < 0)
+ {
+ m_DeadThreadCountForGCTrigger = 0;
+ }
+}
+
+void ThreadStore::OnMaxGenerationGCStarted()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // A dead thread may contribute to triggering a GC at most once. After a max-generation GC occurs, if some dead thread
+ // objects are still reachable due to references to the thread objects, they will not contribute to triggering a GC again.
+ // Synchronize the store with increment/decrement operations occurring on different threads, and make the change visible to
+ // other threads in order to prevent unnecessary GC triggers.
+ FastInterlockExchange(&m_DeadThreadCountForGCTrigger, 0);
+}
+
+bool ThreadStore::ShouldTriggerGCForDeadThreads()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_TriggerGCForDeadThreads;
+}
+
+void ThreadStore::TriggerGCForDeadThreadsIfNecessary()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ if (!m_TriggerGCForDeadThreads)
+ {
+ return;
+ }
+ m_TriggerGCForDeadThreads = false;
+
+ if (g_fEEShutDown)
+ {
+ // Not safe to touch CLR state
+ return;
+ }
+
+ int gcGenerationToTrigger = 0;
+ IGCHeap *gcHeap = GCHeapUtilities::GetGCHeap();
+ _ASSERTE(gcHeap != nullptr);
+ SIZE_T generationCountThreshold = static_cast<SIZE_T>(s_DeadThreadCountThresholdForGCTrigger) / 2;
+ SIZE_T newDeadThreadGenerationCounts[max_generation + 1] = {0};
+ {
+ ThreadStoreLockHolder threadStoreLockHolder;
+ GCX_COOP();
+
+ // Determine the generation for which to trigger a GC. Iterate over all dead threads that have not yet been considered
+ // for triggering a GC and see how many are in which generations.
+ for (Thread *thread = ThreadStore::GetAllThreadList(NULL, Thread::TS_Dead, Thread::TS_Dead);
+ thread != nullptr;
+ thread = ThreadStore::GetAllThreadList(thread, Thread::TS_Dead, Thread::TS_Dead))
+ {
+ if (thread->HasDeadThreadBeenConsideredForGCTrigger())
+ {
+ continue;
+ }
+
+ Object *exposedObject = OBJECTREFToObject(thread->GetExposedObjectRaw());
+ if (exposedObject == nullptr)
+ {
+ continue;
+ }
+
+ int exposedObjectGeneration = gcHeap->WhichGeneration(exposedObject);
+ SIZE_T newDeadThreadGenerationCount = ++newDeadThreadGenerationCounts[exposedObjectGeneration];
+ if (exposedObjectGeneration > gcGenerationToTrigger && newDeadThreadGenerationCount >= generationCountThreshold)
+ {
+ gcGenerationToTrigger = exposedObjectGeneration;
+ if (gcGenerationToTrigger >= max_generation)
+ {
+ break;
+ }
+ }
+ }
+
+ // Make sure that enough time has elapsed since the last GC of the desired generation. We don't want to trigger GCs
+ // based on this heuristic too often. Give it some time to let the memory pressure trigger GCs automatically, and only
+ // if it doesn't in the given time, this heuristic may kick in to trigger a GC.
+ SIZE_T gcLastMilliseconds = gcHeap->GetLastGCStartTime(gcGenerationToTrigger);
+ SIZE_T gcNowMilliseconds = gcHeap->GetNow();
+ if (gcNowMilliseconds - gcLastMilliseconds < s_DeadThreadGCTriggerPeriodMilliseconds)
+ {
+ return;
+ }
+
+ // For threads whose exposed objects are in the generation of GC that will be triggered or in a lower GC generation,
+ // mark them as having contributed to a GC trigger to prevent redundant GC triggers
+ for (Thread *thread = ThreadStore::GetAllThreadList(NULL, Thread::TS_Dead, Thread::TS_Dead);
+ thread != nullptr;
+ thread = ThreadStore::GetAllThreadList(thread, Thread::TS_Dead, Thread::TS_Dead))
+ {
+ if (thread->HasDeadThreadBeenConsideredForGCTrigger())
+ {
+ continue;
+ }
+
+ Object *exposedObject = OBJECTREFToObject(thread->GetExposedObjectRaw());
+ if (exposedObject == nullptr)
+ {
+ continue;
+ }
+
+ if (gcGenerationToTrigger < max_generation &&
+ static_cast<int>(gcHeap->WhichGeneration(exposedObject)) > gcGenerationToTrigger)
+ {
+ continue;
+ }
+
+ thread->SetHasDeadThreadBeenConsideredForGCTrigger();
+ }
+ } // ThreadStoreLockHolder, GCX_COOP()
+
+ GCHeapUtilities::GetGCHeap()->GarbageCollect(gcGenerationToTrigger, FALSE, collection_non_blocking);
+}
+
#endif // #ifndef DACCESS_COMPILE
if (cur->m_State & Thread::TS_DebugSuspendPending)
cntReturn++;
- if (cur->m_State & Thread::TS_UserSuspendPending)
- cntReturn++;
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(cur->m_State & Thread::TS_UserSuspendPending));
if (cur->m_TraceCallCount > 0)
cntReturn++;
|| Thread::CleanupNeededForFinalizedThread()
|| (m_DetachCount > 0)
|| AppDomain::HasWorkForFinalizerThread()
- || SystemDomain::System()->RequireAppDomainCleanup();
+ || SystemDomain::System()->RequireAppDomainCleanup()
+ || ThreadStore::s_pThreadStore->ShouldTriggerGCForDeadThreads();
}
void Thread::DoExtraWorkForFinalizer()
// If there were any TimerInfos waiting to be released, they'll get flushed now
ThreadpoolMgr::FlushQueueOfTimerInfos();
-
+
+ ThreadStore::s_pThreadStore->TriggerGCForDeadThreadsIfNecessary();
}
}
#endif //FEATURE_COMINTEROP
+#ifndef DACCESS_COMPILE
+ bool HasDeadThreadBeenConsideredForGCTrigger()
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(IsDead());
+
+ return m_fHasDeadThreadBeenConsideredForGCTrigger;
+ }
+
+ void SetHasDeadThreadBeenConsideredForGCTrigger()
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(IsDead());
+
+ m_fHasDeadThreadBeenConsideredForGCTrigger = true;
+ }
+#endif // !DACCESS_COMPILE
+
// returns if there is some extra work for the finalizer thread.
BOOL HaveExtraWorkForFinalizer();
void SetupForSuspension(ULONG bit)
{
WRAPPER_NO_CONTRACT;
- if (bit & TS_UserSuspendPending) {
- m_UserSuspendEvent.Reset();
- }
+
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(bit & TS_UserSuspendPending));
+
+
if (bit & TS_DebugSuspendPending) {
m_DebugSuspendEvent.Reset();
}
//
ThreadState oldState = m_State;
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(oldState & TS_UserSuspendPending));
+
while ((oldState & (TS_UserSuspendPending | TS_DebugSuspendPending)) == 0)
{
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(oldState & TS_UserSuspendPending));
+
//
// Construct the destination state we desire - all suspension bits turned off.
//
oldState = m_State;
}
- if (bit & TS_UserSuspendPending) {
- m_UserSuspendEvent.Set();
- }
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(bit & TS_UserSuspendPending));
if (bit & TS_DebugSuspendPending) {
m_DebugSuspendEvent.Set();
}
- // For getting a thread to a safe point. A client waits on the event, which is
- // set by the thread when it reaches a safe spot.
- void SetSafeEvent();
-
public:
FORCEINLINE void UnhijackThreadNoAlloc()
{
private:
// For suspends:
- CLREvent m_SafeEvent;
- CLREvent m_UserSuspendEvent;
CLREvent m_DebugSuspendEvent;
// For Object::Wait, Notify and NotifyAll, we use an Event inside the
// Disables pumping and thread join in RCW creation
bool m_fDisableComObjectEagerCleanup;
+ // See ThreadStore::TriggerGCForDeadThreadsIfNecessary()
+ bool m_fHasDeadThreadBeenConsideredForGCTrigger;
+
private:
CLRRandom m_random;
LONG m_PendingThreadCount;
LONG m_DeadThreadCount;
+ LONG m_DeadThreadCountForGCTrigger;
+ bool m_TriggerGCForDeadThreads;
private:
// Space for the lazily-created GUID.
Thread *m_HoldingThread;
EEThreadId m_holderthreadid; // current holder (or NULL)
+private:
+ static LONG s_DeadThreadCountThresholdForGCTrigger;
+ static DWORD s_DeadThreadGCTriggerPeriodMilliseconds;
+
public:
static BOOL HoldingThreadStore()
LIMITED_METHOD_CONTRACT;
s_pWaitForStackCrawlEvent->Reset();
}
+
+private:
+ void IncrementDeadThreadCountForGCTrigger();
+ void DecrementDeadThreadCountForGCTrigger();
+public:
+ void OnMaxGenerationGCStarted();
+ bool ShouldTriggerGCForDeadThreads();
+ void TriggerGCForDeadThreadsIfNecessary();
};
struct TSSuspendHelper {
m_dwAbortPoint = 7;
#endif
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_UserSuspendPending));
+
//
// If it's stopped by the debugger, we don't want to throw an exception.
// Debugger suspension is to have no effect of the runtime behaviour.
goto Exit;
}
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_UserSuspendPending));
+
// Note IsGCInProgress is also true for say Pause (anywhere SuspendEE happens) and GCThread is the
// thread that did the Pause. While in Pause if another thread attempts Rev/Pinvoke it should get inside the following and
// block until resume
do
{
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_UserSuspendPending));
+
EnablePreemptiveGC();
// Cannot use GCX_PREEMP_NO_DTOR here because we're inside of the thread
#endif // FEATURE_HIJACK
// wake up any threads waiting to suspend us, like the GC thread.
- SetSafeEvent();
ThreadSuspend::g_pGCSuspendEvent->Set();
// for GC, the fact that we are leaving the EE means that it no longer needs to
// Give the debugger precedence over user suspensions:
while (m_State & (TS_DebugSuspendPending | TS_UserSuspendPending))
{
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_UserSuspendPending));
+
#ifdef DEBUGGING_SUPPORTED
// We don't notify the debugger that this thread is now suspended. We'll just
// let the debugger's helper thread sweep and pick it up.
// Enable PGC before calling out to the client to allow runtime suspend to finish
GCX_PREEMP_NO_DTOR();
- // <REVISIT_TODO>@TODO: Is this necessary? Does debugger wait on the events, or does it just
- // poll every so often?</REVISIT_TODO>
- // Notify the thread that is performing the suspension that this thread
- // is now in PGC mode and that it can remove this thread from the list of
- // threads it needs to wait for.
- pThread->SetSafeEvent();
-
// Notify the interface of the pending suspension
switch (reason) {
case RedirectReason_GCSuspension:
LOG((LF_CORDB, LL_INFO1000, "RESUME: resume complete. Trap count: %d\n", g_TrapReturningThreads.Load()));
}
-
-
-void Thread::SetSafeEvent()
-{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- }
- CONTRACTL_END;
-
- m_SafeEvent.Set();
-}
-
-
/*
*
* WaitSuspendEventsHelper
EX_TRY {
- if (m_State & TS_UserSuspendPending) {
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(m_State & TS_UserSuspendPending));
- ThreadState oldState = m_State;
-
- while (oldState & TS_UserSuspendPending) {
-
- ThreadState newState = (ThreadState)(oldState | TS_SyncSuspended);
- if (FastInterlockCompareExchange((LONG *)&m_State, newState, oldState) == (LONG)oldState)
- {
- result = m_UserSuspendEvent.Wait(INFINITE,FALSE);
-#if _DEBUG
- newState = m_State;
- _ASSERTE(!(newState & TS_SyncSuspended) || (newState & TS_DebugSuspendPending));
-#endif
- break;
- }
-
- oldState = m_State;
- }
-
-
- } else if (m_State & TS_DebugSuspendPending) {
+ if (m_State & TS_DebugSuspendPending) {
ThreadState oldState = m_State;
ThreadState oldState = m_State;
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(!(oldState & TS_UserSuspendPending));
+
//
// If all reasons to suspend are off, we think we can exit
// this loop, but we need to check atomically.
}
CONTRACTL_END;
+ // CoreCLR does not support user-requested thread suspension
_ASSERTE(bit == TS_DebugSuspendPending ||
- bit == (TS_DebugSuspendPending | TS_DebugWillSync) ||
- bit == TS_UserSuspendPending);
+ bit == (TS_DebugSuspendPending | TS_DebugWillSync));
_ASSERTE(IsAtProcessExit() || ThreadStore::HoldingThreadStore());
}
CONTRACTL_END;
- _ASSERTE(mask == ~TS_DebugSuspendPending ||
- mask == ~TS_UserSuspendPending);
+ // CoreCLR does not support user-requested thread suspension
+ _ASSERTE(mask == ~TS_DebugSuspendPending);
_ASSERTE(IsAtProcessExit() || ThreadStore::HoldingThreadStore());
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+public class DeadThreads
+{
+ /// <summary>
+ /// A sanity test that exercises code paths relevant to the heuristic that triggers GCs based on dead thread count and time
+ /// elapsed since a previous GC. See https://github.com/dotnet/coreclr/pull/10413.
+ ///
+ /// This test suite runs with the following environment variables relevant to this test (see .csproj):
+ /// set COMPlus_Thread_DeadThreadCountThresholdForGCTrigger=8
+ /// set COMPlus_Thread_DeadThreadGCTriggerPeriodMilliseconds=3e8 // 1000
+ /// </summary>
+ private static void GCTriggerSanityTest()
+ {
+ var testDuration = TimeSpan.FromSeconds(8);
+ var startTime = DateTime.UtcNow;
+ do
+ {
+ StartNoOpThread();
+ Thread.Sleep(1);
+ } while (DateTime.UtcNow - startTime < testDuration);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void StartNoOpThread()
+ {
+ var t = new Thread(() => { });
+ t.IsBackground = true;
+ t.Start();
+ }
+
+ public static int Main()
+ {
+ GCTriggerSanityTest();
+ return 100;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <AssemblyName>DeadThreads</AssemblyName>
+ <ProjectGuid>{649A239B-AB4B-4E20-BDCA-3BA736E8B790}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="DeadThreads.cs" />
+ </ItemGroup>
+ <PropertyGroup>
+ <CLRTestBatchPreCommands><![CDATA[
+$(CLRTestBatchPreCommands)
+set COMPlus_Thread_DeadThreadCountThresholdForGCTrigger=8
+set COMPlus_Thread_DeadThreadGCTriggerPeriodMilliseconds=3e8
+]]></CLRTestBatchPreCommands>
+ <BashCLRTestPreCommands><![CDATA[
+$(BashCLRTestPreCommands)
+export COMPlus_Thread_DeadThreadCountThresholdForGCTrigger=8
+export COMPlus_Thread_DeadThreadGCTriggerPeriodMilliseconds=3e8
+]]></BashCLRTestPreCommands>
+ </PropertyGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>