diff --git a/dom/base/CCGCScheduler.h b/dom/base/CCGCScheduler.h index fd7ea71b510d..8c0787ff7d0a 100644 --- a/dom/base/CCGCScheduler.h +++ b/dom/base/CCGCScheduler.h @@ -94,6 +94,17 @@ struct CCRunnerStep { }; class CCGCScheduler { + // Mockable functions to interface with the code being scheduled. + + // Current time. In real usage, this will just return TimeStamp::Now(), but + // tests can reimplement it to return a value controlled by the test. + static inline mozilla::TimeStamp Now(); + + // Number of entries in the purple buffer (those objects whose ref counts + // have been decremented since the previous CC, roughly), and are therefore + // "suspected" of being members of cyclic garbage. + static inline uint32_t SuspectedCCObjects(); + public: // Parameter setting @@ -165,7 +176,7 @@ class CCGCScheduler { uint32_t NoteForgetSkippableComplete( TimeStamp aNow, uint32_t aSuspectedBeforeForgetSkippable) { mLastForgetSkippableEndTime = aNow; - uint32_t suspected = nsCycleCollector_suspectedCount(); + uint32_t suspected = SuspectedCCObjects(); mPreviousSuspectedCount = suspected; mCleanupsSinceLastGC++; return aSuspectedBeforeForgetSkippable - suspected; @@ -193,7 +204,7 @@ class CCGCScheduler { // The CC was abandoned without running a slice, so we only did forget // skippables. Prevent running another cycle soon. void NoteForgetSkippableOnlyCycle() { - mLastForgetSkippableCycleEndTime = TimeStamp::Now(); + mLastForgetSkippableCycleEndTime = Now(); } void Shutdown() { mDidShutdown = true; } @@ -211,10 +222,10 @@ class CCGCScheduler { inline TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline, TimeStamp aNow) const; - bool ShouldForgetSkippable(uint32_t aSuspected) const { + bool ShouldForgetSkippable() const { // Only do a forget skippable if there are more than a few new objects // or we're doing the initial forget skippables. - return ((mPreviousSuspectedCount + 100) <= aSuspected) || + return ((mPreviousSuspectedCount + 100) <= SuspectedCCObjects()) || mCleanupsSinceLastGC < kMajorForgetSkippableCalls; } @@ -222,10 +233,13 @@ class CCGCScheduler { // garbage to cycle collect: either we just finished a GC, or the purple // buffer is getting really big, or it's getting somewhat big and it has been // too long since the last CC. - bool IsCCNeeded(uint32_t aSuspected, - TimeStamp aNow = TimeStamp::Now()) const { - return mNeedsFullCC || aSuspected > kCCPurpleLimit || - (aSuspected > kCCForcedPurpleLimit && mLastCCEndTime && + bool IsCCNeeded(TimeStamp aNow = Now()) const { + if (mNeedsFullCC) { + return true; + } + uint32_t suspected = SuspectedCCObjects(); + return suspected > kCCPurpleLimit || + (suspected > kCCForcedPurpleLimit && mLastCCEndTime && aNow - mLastCCEndTime > kCCForced); } @@ -278,8 +292,7 @@ class CCGCScheduler { void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; } - inline CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline, - uint32_t aSuspected); + inline CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline); // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some // time ago, if an incremental GC needed to be finished. @@ -333,7 +346,7 @@ class CCGCScheduler { js::SliceBudget CCGCScheduler::ComputeCCSliceBudget( TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime, bool* aPreferShorterSlices) const { - TimeStamp now = TimeStamp::Now(); + TimeStamp now = Now(); *aPreferShorterSlices = aDeadline.IsNull() || (aDeadline - now) < kICCSliceBudget; @@ -398,7 +411,7 @@ bool CCGCScheduler::ShouldScheduleCC() const { return false; } - TimeStamp now = TimeStamp::Now(); + TimeStamp now = Now(); // Don't run consecutive CCs too often. if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) { @@ -417,11 +430,10 @@ bool CCGCScheduler::ShouldScheduleCC() const { } } - return IsCCNeeded(nsCycleCollector_suspectedCount(), now); + return IsCCNeeded(now); } -CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline, - uint32_t aSuspected) { +CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline) { struct StateDescriptor { // When in this state, should we first check to see if we still have // enough reason to CC? @@ -461,7 +473,7 @@ CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline, return {CCRunnerAction::StopRunning, Yield}; } - TimeStamp now = TimeStamp::Now(); + TimeStamp now = Now(); if (InIncrementalGC()) { if (mCCBlockStart.IsNull()) { @@ -497,7 +509,7 @@ CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline, // For states that aren't just continuations of previous states, check // whether a CC is still needed (after doing various things to reduce the // purple buffer). - if (desc.mCanAbortCC && !IsCCNeeded(aSuspected, now)) { + if (desc.mCanAbortCC && !IsCCNeeded(now)) { // If we don't pass the threshold for wanting to cycle collect, stop now // (after possibly doing a final ForgetSkippable). mCCRunnerState = CCRunnerState::Canceled; @@ -505,7 +517,7 @@ CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline, // Preserve the previous code's idea of when to check whether a // ForgetSkippable should be fired. - if (desc.mTryFinalForgetSkippable && ShouldForgetSkippable(aSuspected)) { + if (desc.mTryFinalForgetSkippable && ShouldForgetSkippable()) { // The Canceled state will make us StopRunning after this action is // performed (see conditional at top of function). return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless}; @@ -525,7 +537,7 @@ CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline, mCCRunnerState = CCRunnerState::CleanupChildless; } - if (ShouldForgetSkippable(aSuspected)) { + if (ShouldForgetSkippable()) { return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless}; } @@ -604,7 +616,7 @@ CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline, }; } -js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget( +inline js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget( TimeStamp aStartTimeStamp, TimeStamp aDeadline) { if (mForgetSkippableFrequencyStartTime.IsNull()) { mForgetSkippableFrequencyStartTime = aStartTimeStamp; diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index fc285a8c5272..20c1093d38a3 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -114,6 +114,12 @@ static TimeDuration sGCUnnotifiedTotalTime; static CCGCScheduler sScheduler; +inline TimeStamp mozilla::CCGCScheduler::Now() { return TimeStamp::Now(); } + +inline uint32_t mozilla::CCGCScheduler::SuspectedCCObjects() { + return nsCycleCollector_suspectedCount(); +} + struct CycleCollectorStats { constexpr CycleCollectorStats() = default; void Init(); @@ -1120,14 +1126,14 @@ static void FinishAnyIncrementalGC() { } } -static void FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless, - TimeStamp aDeadline) { +static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) { AUTO_PROFILER_TRACING_MARKER( "CC", aDeadline.IsNull() ? "ForgetSkippable" : "IdleForgetSkippable", GCCC); TimeStamp startTimeStamp = TimeStamp::Now(); FinishAnyIncrementalGC(); + uint32_t suspectedBefore = nsCycleCollector_suspectedCount(); js::SliceBudget budget = sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline); bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable(); @@ -1135,7 +1141,7 @@ static void FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless, earlyForgetSkippable); TimeStamp now = TimeStamp::Now(); uint32_t removedPurples = - sScheduler.NoteForgetSkippableComplete(now, aSuspected); + sScheduler.NoteForgetSkippableComplete(now, suspectedBefore); TimeDuration duration = now - startTimeStamp; @@ -1485,8 +1491,7 @@ void nsJSContext::BeginCycleCollectionCallback() { // is particularly useful if we recently finished a GC. if (sScheduler.IsEarlyForgetSkippable()) { while (sScheduler.IsEarlyForgetSkippable()) { - FireForgetSkippable(nsCycleCollector_suspectedCount(), false, - TimeStamp()); + FireForgetSkippable(false, TimeStamp()); } sCCStats.AfterSyncForgetSkippable(startTime); } @@ -1628,15 +1633,14 @@ static bool CCRunnerFired(TimeStamp aDeadline) { // `Yield` in step.mYield. CCRunnerStep step; do { - uint32_t suspected = nsCycleCollector_suspectedCount(); - step = sScheduler.GetNextCCRunnerAction(aDeadline, suspected); + step = sScheduler.GetNextCCRunnerAction(aDeadline); switch (step.mAction) { case CCRunnerAction::None: break; case CCRunnerAction::ForgetSkippable: // 'Forget skippable' only, then end this invocation. - FireForgetSkippable(suspected, bool(step.mRemoveChildless), aDeadline); + FireForgetSkippable(bool(step.mRemoveChildless), aDeadline); break; case CCRunnerAction::CleanupContentUnbinder: @@ -1952,7 +1956,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, sScheduler.SetNeedsFullGC(false); } - if (sScheduler.IsCCNeeded(nsCycleCollector_suspectedCount())) { + if (sScheduler.IsCCNeeded()) { nsCycleCollector_dispatchDeferredDeletion(); } @@ -1984,7 +1988,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, [] { return sShuttingDown; }); } - if (sScheduler.IsCCNeeded(nsCycleCollector_suspectedCount())) { + if (sScheduler.IsCCNeeded()) { nsCycleCollector_dispatchDeferredDeletion(); }