summaryrefslogtreecommitdiff
path: root/src/vm
diff options
context:
space:
mode:
authorKoundinya Veluri <kouvel@microsoft.com>2017-03-27 11:10:07 -0700
committerGitHub <noreply@github.com>2017-03-27 11:10:07 -0700
commit2401b6ed08252d48831bfd804c3533cd0142c76c (patch)
tree174456e9e6a8a0650abf3af80b722244b9b7449d /src/vm
parent47d333c856e567884594e1efd3497a254b6a04fb (diff)
downloadcoreclr-2401b6ed08252d48831bfd804c3533cd0142c76c.tar.gz
coreclr-2401b6ed08252d48831bfd804c3533cd0142c76c.tar.bz2
coreclr-2401b6ed08252d48831bfd804c3533cd0142c76c.zip
Add heuristic to trigger GC to finalize dead threads and clean up han… (#10413)
Add heuristic to trigger GC to finalize dead threads and clean up handles and memory A thread that is started allocates some handles in Thread::AllocHandles(). After it terminates, the managed thread object needs to be collected by a GC for the handles to be released. Some applications that are mostly idle but have a timer ticking periodically will cause a thread pool thread to be created on each tick, since the thread pool preserves threads for a maximum of 20 seconds currently. Over time the number of handles accumulates to a high value. Thread creation adds some memory pressure, but that does not force a GC until a sufficiently large handle count, and for some mostly idle apps, that can be very long. The same issue arises with directly starting threads as well. Fixes #6602: - Track a dead thread count separately from the current dead thread count. This is the count that will contribute to triggering a GC, once it reaches a threshold. The count is tracked separately so that it may be reset to zero when a max-generation GC occurs, preventing dead threads that survive a GC due to references from contributing to triggering a GC again in this fashion. - If the count exceeds a threshold, enumerate dead threads to see which GC generation the corresponding managed thread objects are in. If the duration of time since the last GC of the desired generation also exceeds a threshold, trigger a preferably non-blocking GC on the finalizer thread. - Removed a couple of handles and some code paths specific to user-requested thread suspension, which is not supported on CoreCLR
Diffstat (limited to 'src/vm')
-rw-r--r--src/vm/comsynchronizable.cpp14
-rw-r--r--src/vm/debugdebugger.cpp58
-rw-r--r--src/vm/eedbginterfaceimpl.cpp11
-rw-r--r--src/vm/finalizerthread.cpp2
-rw-r--r--src/vm/gcenv.ee.cpp5
-rw-r--r--src/vm/threads.cpp257
-rw-r--r--src/vm/threads.h60
-rw-r--r--src/vm/threadsuspend.cpp69
8 files changed, 291 insertions, 185 deletions
diff --git a/src/vm/comsynchronizable.cpp b/src/vm/comsynchronizable.cpp
index b8f871d580..08b5281a42 100644
--- a/src/vm/comsynchronizable.cpp
+++ b/src/vm/comsynchronizable.cpp
@@ -926,18 +926,8 @@ FCIMPL1(INT32, ThreadNative::GetThreadState, ThreadBaseObject* pThisUNSAFE)
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();
diff --git a/src/vm/debugdebugger.cpp b/src/vm/debugdebugger.cpp
index 32c6cd6cce..489c623df9 100644
--- a/src/vm/debugdebugger.cpp
+++ b/src/vm/debugdebugger.cpp
@@ -937,62 +937,8 @@ void DebugStackTrace::GetStackFramesHelper(Frame *pStartFrame,
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);
diff --git a/src/vm/eedbginterfaceimpl.cpp b/src/vm/eedbginterfaceimpl.cpp
index b7a4359350..ede82c7780 100644
--- a/src/vm/eedbginterfaceimpl.cpp
+++ b/src/vm/eedbginterfaceimpl.cpp
@@ -1588,15 +1588,8 @@ CorDebugUserState EEDbgInterfaceImpl::GetPartialUserState(Thread *pThread)
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));
diff --git a/src/vm/finalizerthread.cpp b/src/vm/finalizerthread.cpp
index 51a2ed49c0..d111eebd3c 100644
--- a/src/vm/finalizerthread.cpp
+++ b/src/vm/finalizerthread.cpp
@@ -1345,6 +1345,8 @@ BOOL FinalizerThread::FinalizerThreadWatchDogHelper()
}
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)
diff --git a/src/vm/gcenv.ee.cpp b/src/vm/gcenv.ee.cpp
index bf2ce2c74d..392c179243 100644
--- a/src/vm/gcenv.ee.cpp
+++ b/src/vm/gcenv.ee.cpp
@@ -626,6 +626,11 @@ void GCToEEInterface::GcStartWork (int condemned, int max_gen)
//
RCWWalker::OnGCStarted(condemned);
#endif // FEATURE_COMINTEROP
+
+ if (condemned == max_gen)
+ {
+ ThreadStore::s_pThreadStore->OnMaxGenerationGCStarted();
+ }
}
void GCToEEInterface::GcDone(int condemned)
diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp
index b5f750778b..6d9d10b761 100644
--- a/src/vm/threads.cpp
+++ b/src/vm/threads.cpp
@@ -1825,6 +1825,7 @@ Thread::Thread()
#ifdef FEATURE_COMINTEROP
m_fDisableComObjectEagerCleanup = false;
#endif //FEATURE_COMINTEROP
+ m_fHasDeadThreadBeenConsideredForGCTrigger = false;
m_Context = NULL;
m_TraceCallCount = 0;
m_ThrewControlForThread = 0;
@@ -2170,28 +2171,17 @@ BOOL Thread::AllocHandles()
{
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();
@@ -2404,30 +2394,8 @@ FAILURE:
}
#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;
@@ -3064,14 +3032,6 @@ Thread::~Thread()
CloseHandle(GetThreadHandle());
}
- if (m_SafeEvent.IsValid())
- {
- m_SafeEvent.CloseEvent();
- }
- if (m_UserSuspendEvent.IsValid())
- {
- m_UserSuspendEvent.CloseEvent();
- }
if (m_DebugSuspendEvent.IsValid())
{
m_DebugSuspendEvent.CloseEvent();
@@ -3503,6 +3463,7 @@ void Thread::OnThreadTerminate(BOOL holdingLock)
FastInterlockOr((ULONG *) &m_State, TS_Dead);
ThreadStore::s_pThreadStore->m_DeadThreadCount++;
+ ThreadStore::s_pThreadStore->IncrementDeadThreadCountForGCTrigger();
if (IsUnstarted())
ThreadStore::s_pThreadStore->m_UnstartedThreadCount--;
@@ -3527,8 +3488,8 @@ void Thread::OnThreadTerminate(BOOL holdingLock)
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())
{
@@ -5739,6 +5700,8 @@ ThreadStore::ThreadStore()
m_BackgroundThreadCount(0),
m_PendingThreadCount(0),
m_DeadThreadCount(0),
+ m_DeadThreadCountForGCTrigger(0),
+ m_TriggerGCForDeadThreads(false),
m_GuidCreated(FALSE),
m_HoldingThread(0)
{
@@ -5778,6 +5741,15 @@ void ThreadStore::InitThreadStore()
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
@@ -5920,7 +5892,10 @@ BOOL ThreadStore::RemoveThread(Thread *target)
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())
@@ -6009,6 +5984,188 @@ void ThreadStore::TransferStartedThread(Thread *thread, BOOL bRequiresTSL)
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
@@ -6226,8 +6383,8 @@ Retry:
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++;
@@ -8697,7 +8854,8 @@ BOOL Thread::HaveExtraWorkForFinalizer()
|| Thread::CleanupNeededForFinalizedThread()
|| (m_DetachCount > 0)
|| AppDomain::HasWorkForFinalizerThread()
- || SystemDomain::System()->RequireAppDomainCleanup();
+ || SystemDomain::System()->RequireAppDomainCleanup()
+ || ThreadStore::s_pThreadStore->ShouldTriggerGCForDeadThreads();
}
void Thread::DoExtraWorkForFinalizer()
@@ -8754,7 +8912,8 @@ void Thread::DoExtraWorkForFinalizer()
// If there were any TimerInfos waiting to be released, they'll get flushed now
ThreadpoolMgr::FlushQueueOfTimerInfos();
-
+
+ ThreadStore::s_pThreadStore->TriggerGCForDeadThreadsIfNecessary();
}
diff --git a/src/vm/threads.h b/src/vm/threads.h
index ff0d1669ce..f34066feb3 100644
--- a/src/vm/threads.h
+++ b/src/vm/threads.h
@@ -1450,6 +1450,24 @@ public:
}
#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();
@@ -3712,9 +3730,11 @@ private:
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();
}
@@ -3731,8 +3751,14 @@ private:
//
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.
//
@@ -3751,9 +3777,8 @@ private:
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();
@@ -3761,10 +3786,6 @@ private:
}
- // 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()
{
@@ -3885,8 +3906,6 @@ public:
private:
// For suspends:
- CLREvent m_SafeEvent;
- CLREvent m_UserSuspendEvent;
CLREvent m_DebugSuspendEvent;
// For Object::Wait, Notify and NotifyAll, we use an Event inside the
@@ -5230,6 +5249,9 @@ private:
// Disables pumping and thread join in RCW creation
bool m_fDisableComObjectEagerCleanup;
+ // See ThreadStore::TriggerGCForDeadThreadsIfNecessary()
+ bool m_fHasDeadThreadBeenConsideredForGCTrigger;
+
private:
CLRRandom m_random;
@@ -5516,6 +5538,8 @@ private:
LONG m_PendingThreadCount;
LONG m_DeadThreadCount;
+ LONG m_DeadThreadCountForGCTrigger;
+ bool m_TriggerGCForDeadThreads;
private:
// Space for the lazily-created GUID.
@@ -5528,6 +5552,10 @@ private:
Thread *m_HoldingThread;
EEThreadId m_holderthreadid; // current holder (or NULL)
+private:
+ static LONG s_DeadThreadCountThresholdForGCTrigger;
+ static DWORD s_DeadThreadGCTriggerPeriodMilliseconds;
+
public:
static BOOL HoldingThreadStore()
@@ -5601,6 +5629,14 @@ public:
LIMITED_METHOD_CONTRACT;
s_pWaitForStackCrawlEvent->Reset();
}
+
+private:
+ void IncrementDeadThreadCountForGCTrigger();
+ void DecrementDeadThreadCountForGCTrigger();
+public:
+ void OnMaxGenerationGCStarted();
+ bool ShouldTriggerGCForDeadThreads();
+ void TriggerGCForDeadThreadsIfNecessary();
};
struct TSSuspendHelper {
diff --git a/src/vm/threadsuspend.cpp b/src/vm/threadsuspend.cpp
index 10032fd08f..71409ceb5a 100644
--- a/src/vm/threadsuspend.cpp
+++ b/src/vm/threadsuspend.cpp
@@ -1978,6 +1978,9 @@ LRetry:
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.
@@ -3090,6 +3093,9 @@ void Thread::RareDisablePreemptiveGC()
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
@@ -3105,6 +3111,9 @@ void Thread::RareDisablePreemptiveGC()
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
@@ -3558,7 +3567,6 @@ void Thread::RareEnablePreemptiveGC()
#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
@@ -3566,6 +3574,9 @@ void Thread::RareEnablePreemptiveGC()
// 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.
@@ -3903,13 +3914,6 @@ void __stdcall Thread::RedirectedHandledJITCase(RedirectReason reason)
// 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:
@@ -6016,20 +6020,6 @@ void Thread::SysResumeFromDebug(AppDomain *pAppDomain)
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
@@ -6056,28 +6046,10 @@ BOOL Thread::WaitSuspendEventsHelper(void)
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;
@@ -6127,6 +6099,9 @@ void Thread::WaitSuspendEvents(BOOL fDoWait)
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.
@@ -7106,9 +7081,9 @@ void Thread::MarkForSuspension(ULONG bit)
}
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());
@@ -7126,8 +7101,8 @@ void Thread::UnmarkForSuspension(ULONG mask)
}
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());