diff options
author | Koundinya Veluri <kouvel@microsoft.com> | 2017-03-27 11:10:07 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-27 11:10:07 -0700 |
commit | 2401b6ed08252d48831bfd804c3533cd0142c76c (patch) | |
tree | 174456e9e6a8a0650abf3af80b722244b9b7449d /src/vm/threads.h | |
parent | 47d333c856e567884594e1efd3497a254b6a04fb (diff) | |
download | coreclr-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.h | 60 |
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 { |