Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / CCGCScheduler.h
blob87b7f16f10df6ffa4fc6fc5648fe16f7d7494e80
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "js/SliceBudget.h"
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/CycleCollectedJSContext.h"
8 #include "mozilla/IdleTaskRunner.h"
9 #include "mozilla/MainThreadIdlePeriod.h"
10 #include "mozilla/Telemetry.h"
11 #include "mozilla/TimeStamp.h"
12 #include "mozilla/ipc/IdleSchedulerChild.h"
13 #include "nsCycleCollector.h"
14 #include "nsJSEnvironment.h"
15 #include "nsCycleCollectionParticipant.h"
17 namespace mozilla {
19 extern const TimeDuration kOneMinute;
21 // The amount of time we wait between a request to CC (after GC ran)
22 // and doing the actual CC.
23 extern const TimeDuration kCCDelay;
25 extern const TimeDuration kCCSkippableDelay;
27 // In case the cycle collector isn't run at all, we don't want forget skippables
28 // to run too often. So limit the forget skippable cycle to start at earliest 2
29 // seconds after the end of the previous cycle.
30 extern const TimeDuration kTimeBetweenForgetSkippableCycles;
32 // ForgetSkippable is usually fast, so we can use small budgets.
33 // This isn't a real budget but a hint to IdleTaskRunner whether there
34 // is enough time to call ForgetSkippable.
35 extern const TimeDuration kForgetSkippableSliceDuration;
37 // Maximum amount of time that should elapse between incremental CC slices
38 extern const TimeDuration kICCIntersliceDelay;
40 // Time budget for an incremental CC slice when using timer to run it.
41 extern const TimeDuration kICCSliceBudget;
42 // Minimum budget for an incremental CC slice when using idle time to run it.
43 extern const TimeDuration kIdleICCSliceBudget;
45 // Maximum total duration for an ICC
46 extern const TimeDuration kMaxICCDuration;
48 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
49 // objects in the purple buffer.
50 extern const TimeDuration kCCForced;
51 constexpr uint32_t kCCForcedPurpleLimit = 10;
53 // Don't allow an incremental GC to lock out the CC for too long.
54 extern const TimeDuration kMaxCCLockedoutTime;
56 // Trigger a CC if the purple buffer exceeds this size when we check it.
57 constexpr uint32_t kCCPurpleLimit = 200;
59 // Actions performed by the GCRunner state machine.
60 enum class GCRunnerAction {
61 MinorGC, // Run a minor GC (nursery collection)
62 WaitToMajorGC, // We want to start a new major GC
63 StartMajorGC, // The parent says we may begin our major GC
64 GCSlice, // Run a single slice of a major GC
65 None
68 struct GCRunnerStep {
69 GCRunnerAction mAction;
70 JS::GCReason mReason;
73 // Actions that are output from the CCRunner state machine.
74 enum class CCRunnerAction {
75 // Do nothing.
76 None,
78 // We crossed an eager minor GC threshold in the middle of an incremental CC,
79 // and we have some idle time.
80 MinorGC,
82 // Various cleanup actions.
83 ForgetSkippable,
84 CleanupContentUnbinder,
85 CleanupDeferred,
87 // Do the actual cycle collection (build the graph etc).
88 CycleCollect,
90 // All done.
91 StopRunning
94 enum CCRunnerYield { Continue, Yield };
96 enum CCRunnerForgetSkippableRemoveChildless {
97 KeepChildless = false,
98 RemoveChildless = true
101 struct CCRunnerStep {
102 // The action the scheduler is instructing the caller to perform.
103 CCRunnerAction mAction;
105 // Whether to stop processing actions for this invocation of the timer
106 // callback.
107 CCRunnerYield mYield;
109 union ActionData {
110 // If the action is ForgetSkippable, then whether to remove childless nodes
111 // or not.
112 CCRunnerForgetSkippableRemoveChildless mRemoveChildless;
114 // If the action is CycleCollect, the reason for the collection.
115 CCReason mCCReason;
117 // If the action is MinorGC, the reason for the GC.
118 JS::GCReason mReason;
120 MOZ_IMPLICIT ActionData(CCRunnerForgetSkippableRemoveChildless v)
121 : mRemoveChildless(v) {}
122 MOZ_IMPLICIT ActionData(CCReason v) : mCCReason(v) {}
123 MOZ_IMPLICIT ActionData(JS::GCReason v) : mReason(v) {}
124 ActionData() = default;
125 } mParam;
128 class CCGCScheduler {
129 public:
130 CCGCScheduler()
131 : mAskParentBeforeMajorGC(XRE_IsContentProcess()),
132 mReadyForMajorGC(!mAskParentBeforeMajorGC),
133 mInterruptRequested(false) {}
135 static bool CCRunnerFired(TimeStamp aDeadline);
137 // Parameter setting
139 void SetActiveIntersliceGCBudget(TimeDuration aDuration) {
140 mActiveIntersliceGCBudget = aDuration;
143 // State retrieval
145 TimeDuration GetCCBlockedTime(TimeStamp aNow) const {
146 MOZ_ASSERT(mInIncrementalGC);
147 MOZ_ASSERT(!mCCBlockStart.IsNull());
148 return aNow - mCCBlockStart;
151 bool InIncrementalGC() const { return mInIncrementalGC; }
153 TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; }
155 bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const {
156 return mCleanupsSinceLastGC < aN;
159 bool NeedsFullGC() const { return mNeedsFullGC; }
161 // Requests
162 void PokeGC(JS::GCReason aReason, JSObject* aObj, TimeDuration aDelay = 0);
163 void PokeShrinkingGC();
164 void PokeFullGC();
165 void MaybePokeCC(TimeStamp aNow, uint32_t aSuspectedCCObjects);
166 void PokeMinorGC(JS::GCReason aReason);
168 void UserIsInactive();
169 void UserIsActive();
170 bool IsUserActive() const { return mUserIsActive; }
172 void KillShrinkingGCTimer();
173 void KillFullGCTimer();
174 void KillGCRunner();
175 void KillCCRunner();
176 void KillAllTimersAndRunners();
178 js::SliceBudget CreateGCSliceBudget(mozilla::TimeDuration aDuration,
179 bool isIdle, bool isExtended) {
180 mInterruptRequested = false;
181 auto budget = js::SliceBudget(aDuration, &mInterruptRequested);
182 budget.idle = isIdle;
183 budget.extended = isExtended;
184 return budget;
188 * aDelay is the delay before the first time the idle task runner runs.
189 * Then it runs every
190 * StaticPrefs::javascript_options_gc_delay_interslice()
192 void EnsureGCRunner(TimeDuration aDelay);
194 // If GCRunner isn't active, this calls EnsureGCRunner(0). Otherwise the timer
195 // is reset.
196 void EnsureOrResetGCRunner();
198 void EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget);
200 // State modification
202 void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; }
204 void SetWantMajorGC(JS::GCReason aReason) {
205 MOZ_ASSERT(aReason != JS::GCReason::NO_REASON);
207 // If the GC being requested is not a shrinking GC set this flag.
208 // If/when the shrinking GC timer fires but the user is active we check
209 // this flag before canceling the GC, so as not to cancel the
210 // non-shrinking GC being requested here.
211 if (aReason != JS::GCReason::USER_INACTIVE) {
212 mWantAtLeastRegularGC = true;
215 // Force full GCs when called from reftests so that we collect dead zones
216 // that have not been scheduled for collection.
217 if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
218 SetNeedsFullGC();
221 // USER_INACTIVE trumps everything,
222 // FULL_GC_TIMER trumps everything except USER_INACTIVE,
223 // all other reasons just use the latest reason.
224 switch (aReason) {
225 case JS::GCReason::USER_INACTIVE:
226 mMajorGCReason = aReason;
227 break;
228 case JS::GCReason::FULL_GC_TIMER:
229 if (mMajorGCReason != JS::GCReason::USER_INACTIVE) {
230 mMajorGCReason = aReason;
232 break;
233 default:
234 if (mMajorGCReason != JS::GCReason::USER_INACTIVE &&
235 mMajorGCReason != JS::GCReason::FULL_GC_TIMER) {
236 mMajorGCReason = aReason;
238 break;
242 void SetWantEagerMinorGC(JS::GCReason aReason) {
243 if (mEagerMinorGCReason == JS::GCReason::NO_REASON) {
244 mEagerMinorGCReason = aReason;
248 // Ensure that the current runner does a cycle collection, and trigger a GC
249 // after it finishes.
250 void EnsureCCThenGC(CCReason aReason) {
251 MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
252 MOZ_ASSERT(aReason != CCReason::NO_REASON);
253 mNeedsFullCC = aReason;
254 mNeedsGCAfterCC = true;
257 // Returns false if we started and finished a major GC while waiting for a
258 // response.
259 [[nodiscard]] bool NoteReadyForMajorGC() {
260 if (mMajorGCReason == JS::GCReason::NO_REASON || InIncrementalGC()) {
261 return false;
263 mReadyForMajorGC = true;
264 return true;
267 // Starting a major GC (incremental or non-incremental).
268 void NoteGCBegin(JS::GCReason aReason);
270 // Major GC completed.
271 void NoteGCEnd();
273 // A timer fired, but then decided not to run a GC.
274 void NoteWontGC();
276 void NoteMinorGCEnd() { mEagerMinorGCReason = JS::GCReason::NO_REASON; }
278 // This is invoked when we reach the actual cycle collection portion of the
279 // overall cycle collection.
280 void NoteCCBegin(CCReason aReason, TimeStamp aWhen,
281 uint32_t aNumForgetSkippables, uint32_t aSuspected,
282 uint32_t aRemovedPurples);
284 // This is invoked when the whole process of collection is done -- i.e., CC
285 // preparation (eg ForgetSkippables) in addition to the CC itself. There
286 // really ought to be a separate name for the overall CC as opposed to the
287 // actual cycle collection portion.
288 void NoteCCEnd(const CycleCollectorResults& aResults, TimeStamp aWhen,
289 mozilla::TimeDuration aMaxSliceTime);
291 // A single slice has completed.
292 void NoteGCSliceEnd(TimeStamp aStart, TimeStamp aEnd);
294 bool GCRunnerFired(TimeStamp aDeadline);
295 bool GCRunnerFiredDoGC(TimeStamp aDeadline, const GCRunnerStep& aStep);
297 using MayGCPromise =
298 MozPromise<bool, mozilla::ipc::ResponseRejectReason, true>;
300 // Returns null if we shouldn't GC now (eg a GC is already running).
301 static RefPtr<MayGCPromise> MayGCNow(JS::GCReason reason);
303 // Check all of the various collector timers/runners and see if they are
304 // waiting to fire. This does not check the Full GC Timer, as that's a
305 // more expensive collection we run on a long timer.
306 void RunNextCollectorTimer(JS::GCReason aReason,
307 mozilla::TimeStamp aDeadline);
309 // When we decide to do a cycle collection but we're in the middle of an
310 // incremental GC, the CC is "locked out" until the GC completes -- unless
311 // the wait is too long, and we decide to finish the incremental GC early.
312 void BlockCC(TimeStamp aNow) {
313 MOZ_ASSERT(mInIncrementalGC);
314 MOZ_ASSERT(mCCBlockStart.IsNull());
315 mCCBlockStart = aNow;
318 void UnblockCC() { mCCBlockStart = TimeStamp(); }
320 // Returns the number of purple buffer items that were processed and removed.
321 uint32_t NoteForgetSkippableComplete(TimeStamp aNow,
322 uint32_t aSuspectedBeforeForgetSkippable,
323 uint32_t aSuspectedCCObjects) {
324 mLastForgetSkippableEndTime = aNow;
325 mPreviousSuspectedCount = aSuspectedCCObjects;
326 mCleanupsSinceLastGC++;
327 return aSuspectedBeforeForgetSkippable - aSuspectedCCObjects;
330 // Test if we are in the NoteCCBegin .. NoteCCEnd interval.
331 bool IsCollectingCycles() const { return mIsCollectingCycles; }
333 // The CC was abandoned without running a slice, so we only did forget
334 // skippables. Prevent running another cycle soon.
335 void NoteForgetSkippableOnlyCycle(TimeStamp aNow) {
336 mLastForgetSkippableCycleEndTime = aNow;
339 void Shutdown() {
340 mDidShutdown = true;
341 KillAllTimersAndRunners();
344 // Scheduling
346 // Return a budget along with a boolean saying whether to prefer to run short
347 // slices and stop rather than continuing to the next phase of cycle
348 // collection.
349 js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
350 TimeStamp aCCBeginTime,
351 TimeStamp aPrevSliceEndTime,
352 TimeStamp aNow,
353 bool* aPreferShorterSlices) const;
355 js::SliceBudget ComputeInterSliceGCBudget(TimeStamp aDeadline,
356 TimeStamp aNow);
358 bool ShouldForgetSkippable(uint32_t aSuspectedCCObjects) const {
359 // Only do a forget skippable if there are more than a few new objects
360 // or we're doing the initial forget skippables.
361 return ((mPreviousSuspectedCount + 100) <= aSuspectedCCObjects) ||
362 mCleanupsSinceLastGC < kMajorForgetSkippableCalls;
365 // There is reason to suspect that there may be a significant amount of
366 // garbage to cycle collect: either we just finished a GC, or the purple
367 // buffer is getting really big, or it's getting somewhat big and it has been
368 // too long since the last CC.
369 CCReason IsCCNeeded(TimeStamp aNow, uint32_t aSuspectedCCObjects) const {
370 if (mNeedsFullCC != CCReason::NO_REASON) {
371 return mNeedsFullCC;
373 if (aSuspectedCCObjects > kCCPurpleLimit) {
374 return CCReason::MANY_SUSPECTED;
376 if (aSuspectedCCObjects > kCCForcedPurpleLimit && mLastCCEndTime &&
377 aNow - mLastCCEndTime > kCCForced) {
378 return CCReason::TIMED;
380 return CCReason::NO_REASON;
383 mozilla::CCReason ShouldScheduleCC(TimeStamp aNow,
384 uint32_t aSuspectedCCObjects) const;
386 // If we collected a substantial amount of cycles, poke the GC since more
387 // objects might be unreachable now.
388 bool NeedsGCAfterCC() const {
389 return mCCollectedWaitingForGC > 250 || mCCollectedZonesWaitingForGC > 0 ||
390 mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC;
393 bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const {
394 int32_t numEarlyTimerFires =
395 std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
397 return aCurrentFireCount >= numEarlyTimerFires;
400 enum class CCRunnerState {
401 Inactive,
402 ReducePurple,
403 CleanupChildless,
404 CleanupContentUnbinder,
405 CleanupDeferred,
406 StartCycleCollection,
407 CycleCollecting,
408 Canceled,
409 NumStates
412 void InitCCRunnerStateMachine(CCRunnerState initialState, CCReason aReason) {
413 if (mCCRunner) {
414 return;
417 MOZ_ASSERT(mCCReason == CCReason::NO_REASON);
418 mCCReason = aReason;
420 // The state machine should always have been deactivated after the previous
421 // collection, however far that collection may have gone.
422 MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive,
423 "DeactivateCCRunner should have been called");
424 mCCRunnerState = initialState;
426 // Currently, there are only two entry points to the non-Inactive part of
427 // the state machine.
428 if (initialState == CCRunnerState::ReducePurple) {
429 mCCDelay = kCCDelay;
430 mCCRunnerEarlyFireCount = 0;
431 } else if (initialState == CCRunnerState::CycleCollecting) {
432 // Nothing needed.
433 } else {
434 MOZ_CRASH("Invalid initial state");
438 void DeactivateCCRunner() {
439 mCCRunnerState = CCRunnerState::Inactive;
440 mCCReason = CCReason::NO_REASON;
443 bool HasMoreIdleGCRunnerWork() const {
444 return mMajorGCReason != JS::GCReason::NO_REASON ||
445 mEagerMajorGCReason != JS::GCReason::NO_REASON ||
446 mEagerMinorGCReason != JS::GCReason::NO_REASON;
449 GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline) const;
451 CCRunnerStep AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow,
452 uint32_t aSuspectedCCObjects);
454 // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some
455 // time ago, if an incremental GC needed to be finished.
456 js::SliceBudget ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp,
457 TimeStamp aDeadline);
459 private:
460 // State
462 // An incremental GC is in progress, which blocks the CC from running for its
463 // duration (or until it goes too long and is finished synchronously.)
464 bool mInIncrementalGC = false;
466 // Whether to ask the parent process if now is a good time to GC (false for
467 // the parent process.)
468 const bool mAskParentBeforeMajorGC;
470 // We've asked the parent process if now is a good time to GC (do not ask
471 // again).
472 bool mHaveAskedParent = false;
474 // The parent process is ready for us to do a major GC.
475 bool mReadyForMajorGC;
477 // Set when the IdleTaskRunner requests the current task be interrupted.
478 // Cleared when the GC slice budget has detected the interrupt request.
479 js::SliceBudget::InterruptRequestFlag mInterruptRequested;
481 // When a shrinking GC has been requested but we back-out, if this is true
482 // we run a non-shrinking GC.
483 bool mWantAtLeastRegularGC = false;
485 // When the CC started actually waiting for the GC to finish. This will be
486 // set to non-null at a later time than mCCLockedOut.
487 TimeStamp mCCBlockStart;
489 bool mDidShutdown = false;
491 TimeStamp mLastForgetSkippableEndTime;
492 uint32_t mForgetSkippableCounter = 0;
493 TimeStamp mForgetSkippableFrequencyStartTime;
494 TimeStamp mLastCCEndTime;
495 TimeStamp mLastForgetSkippableCycleEndTime;
497 CCRunnerState mCCRunnerState = CCRunnerState::Inactive;
498 int32_t mCCRunnerEarlyFireCount = 0;
499 TimeDuration mCCDelay = kCCDelay;
501 // Prevent the very first CC from running before we have GC'd and set the
502 // gray bits.
503 bool mHasRunGC = false;
505 mozilla::CCReason mNeedsFullCC = CCReason::NO_REASON;
506 bool mNeedsFullGC = true;
507 bool mNeedsGCAfterCC = false;
508 uint32_t mPreviousSuspectedCount = 0;
510 uint32_t mCleanupsSinceLastGC = UINT32_MAX;
512 // If the GC runner triggers a GC slice, this will be set to the idle deadline
513 // or the null timestamp if non-idle. It will be Nothing at the end of an
514 // internally-triggered slice.
515 mozilla::Maybe<TimeStamp> mTriggeredGCDeadline;
517 RefPtr<IdleTaskRunner> mGCRunner;
518 RefPtr<IdleTaskRunner> mCCRunner;
519 nsITimer* mShrinkingGCTimer = nullptr;
520 nsITimer* mFullGCTimer = nullptr;
522 mozilla::CCReason mCCReason = mozilla::CCReason::NO_REASON;
523 JS::GCReason mMajorGCReason = JS::GCReason::NO_REASON;
524 JS::GCReason mEagerMajorGCReason = JS::GCReason::NO_REASON;
525 JS::GCReason mEagerMinorGCReason = JS::GCReason::NO_REASON;
527 bool mIsCompactingOnUserInactive = false;
528 bool mIsCollectingCycles = false;
529 bool mUserIsActive = true;
531 public:
532 uint32_t mCCollectedWaitingForGC = 0;
533 uint32_t mCCollectedZonesWaitingForGC = 0;
534 uint32_t mLikelyShortLivingObjectsNeedingGC = 0;
536 // Configuration parameters
538 TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);
541 } // namespace mozilla