summaryrefslogtreecommitdiff
path: root/src/vm/threads.h
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/threads.h
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/threads.h')
-rw-r--r--src/vm/threads.h60
1 files changed, 48 insertions, 12 deletions
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 {