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"
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
69 GCRunnerAction mAction
;
73 // Actions that are output from the CCRunner state machine.
74 enum class CCRunnerAction
{
78 // We crossed an eager minor GC threshold in the middle of an incremental CC,
79 // and we have some idle time.
82 // Various cleanup actions.
84 CleanupContentUnbinder
,
87 // Do the actual cycle collection (build the graph etc).
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
107 CCRunnerYield mYield
;
110 // If the action is ForgetSkippable, then whether to remove childless nodes
112 CCRunnerForgetSkippableRemoveChildless mRemoveChildless
;
114 // If the action is CycleCollect, the reason for the collection.
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;
128 class CCGCScheduler
{
131 : mAskParentBeforeMajorGC(XRE_IsContentProcess()),
132 mReadyForMajorGC(!mAskParentBeforeMajorGC
),
133 mInterruptRequested(false) {}
135 static bool CCRunnerFired(TimeStamp aDeadline
);
139 void SetActiveIntersliceGCBudget(TimeDuration aDuration
) {
140 mActiveIntersliceGCBudget
= aDuration
;
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
; }
162 void PokeGC(JS::GCReason aReason
, JSObject
* aObj
, TimeDuration aDelay
= 0);
163 void PokeShrinkingGC();
165 void MaybePokeCC(TimeStamp aNow
, uint32_t aSuspectedCCObjects
);
166 void PokeMinorGC(JS::GCReason aReason
);
168 void UserIsInactive();
170 bool IsUserActive() const { return mUserIsActive
; }
172 void KillShrinkingGCTimer();
173 void KillFullGCTimer();
176 void KillAllTimersAndRunners();
178 js::SliceBudget
CreateGCSliceBudget(mozilla::TimeDuration aDuration
,
179 bool isIdle
, bool isExtended
) {
180 auto budget
= js::SliceBudget(aDuration
, &mInterruptRequested
);
181 budget
.idle
= isIdle
;
182 budget
.extended
= isExtended
;
187 * aDelay is the delay before the first time the idle task runner runs.
189 * StaticPrefs::javascript_options_gc_delay_interslice()
191 void EnsureGCRunner(TimeDuration aDelay
);
193 void EnsureCCRunner(TimeDuration aDelay
, TimeDuration aBudget
);
195 // State modification
197 void SetNeedsFullGC(bool aNeedGC
= true) { mNeedsFullGC
= aNeedGC
; }
199 void SetWantMajorGC(JS::GCReason aReason
) {
200 MOZ_ASSERT(aReason
!= JS::GCReason::NO_REASON
);
202 // If the GC being requested is not a shrinking GC set this flag.
203 // If/when the shrinking GC timer fires but the user is active we check
204 // this flag before canceling the GC, so as not to cancel the
205 // non-shrinking GC being requested here.
206 if (aReason
!= JS::GCReason::USER_INACTIVE
) {
207 mWantAtLeastRegularGC
= true;
210 // Force full GCs when called from reftests so that we collect dead zones
211 // that have not been scheduled for collection.
212 if (aReason
== JS::GCReason::DOM_WINDOW_UTILS
) {
216 // USER_INACTIVE trumps everything,
217 // FULL_GC_TIMER trumps everything except USER_INACTIVE,
218 // all other reasons just use the latest reason.
220 case JS::GCReason::USER_INACTIVE
:
221 mMajorGCReason
= aReason
;
223 case JS::GCReason::FULL_GC_TIMER
:
224 if (mMajorGCReason
!= JS::GCReason::USER_INACTIVE
) {
225 mMajorGCReason
= aReason
;
229 if (mMajorGCReason
!= JS::GCReason::USER_INACTIVE
&&
230 mMajorGCReason
!= JS::GCReason::FULL_GC_TIMER
) {
231 mMajorGCReason
= aReason
;
237 void SetWantEagerMinorGC(JS::GCReason aReason
) {
238 if (mEagerMinorGCReason
== JS::GCReason::NO_REASON
) {
239 mEagerMinorGCReason
= aReason
;
243 // Ensure that the current runner does a cycle collection, and trigger a GC
244 // after it finishes.
245 void EnsureCCThenGC(CCReason aReason
) {
246 MOZ_ASSERT(mCCRunnerState
!= CCRunnerState::Inactive
);
247 MOZ_ASSERT(aReason
!= CCReason::NO_REASON
);
248 mNeedsFullCC
= aReason
;
249 mNeedsGCAfterCC
= true;
252 // Returns false if we started and finished a major GC while waiting for a
254 [[nodiscard
]] bool NoteReadyForMajorGC() {
255 if (mMajorGCReason
== JS::GCReason::NO_REASON
|| InIncrementalGC()) {
258 mReadyForMajorGC
= true;
262 // Starting a major GC (incremental or non-incremental).
263 void NoteGCBegin(JS::GCReason aReason
);
265 // Major GC completed.
268 // A timer fired, but then decided not to run a GC.
271 void NoteMinorGCEnd() { mEagerMinorGCReason
= JS::GCReason::NO_REASON
; }
273 // This is invoked when we reach the actual cycle collection portion of the
274 // overall cycle collection.
275 void NoteCCBegin(CCReason aReason
, TimeStamp aWhen
,
276 uint32_t aNumForgetSkippables
, uint32_t aSuspected
,
277 uint32_t aRemovedPurples
);
279 // This is invoked when the whole process of collection is done -- i.e., CC
280 // preparation (eg ForgetSkippables) in addition to the CC itself. There
281 // really ought to be a separate name for the overall CC as opposed to the
282 // actual cycle collection portion.
283 void NoteCCEnd(const CycleCollectorResults
& aResults
, TimeStamp aWhen
,
284 mozilla::TimeDuration aMaxSliceTime
);
286 // A single slice has completed.
287 void NoteGCSliceEnd(TimeStamp aStart
, TimeStamp aEnd
);
289 bool GCRunnerFired(TimeStamp aDeadline
);
290 bool GCRunnerFiredDoGC(TimeStamp aDeadline
, const GCRunnerStep
& aStep
);
293 MozPromise
<bool, mozilla::ipc::ResponseRejectReason
, true>;
295 // Returns null if we shouldn't GC now (eg a GC is already running).
296 static RefPtr
<MayGCPromise
> MayGCNow(JS::GCReason reason
);
298 // Check all of the various collector timers/runners and see if they are
299 // waiting to fire. This does not check the Full GC Timer, as that's a
300 // more expensive collection we run on a long timer.
301 void RunNextCollectorTimer(JS::GCReason aReason
,
302 mozilla::TimeStamp aDeadline
);
304 // When we decide to do a cycle collection but we're in the middle of an
305 // incremental GC, the CC is "locked out" until the GC completes -- unless
306 // the wait is too long, and we decide to finish the incremental GC early.
307 void BlockCC(TimeStamp aNow
) {
308 MOZ_ASSERT(mInIncrementalGC
);
309 MOZ_ASSERT(mCCBlockStart
.IsNull());
310 mCCBlockStart
= aNow
;
313 void UnblockCC() { mCCBlockStart
= TimeStamp(); }
315 // Returns the number of purple buffer items that were processed and removed.
316 uint32_t NoteForgetSkippableComplete(TimeStamp aNow
,
317 uint32_t aSuspectedBeforeForgetSkippable
,
318 uint32_t aSuspectedCCObjects
) {
319 mLastForgetSkippableEndTime
= aNow
;
320 mPreviousSuspectedCount
= aSuspectedCCObjects
;
321 mCleanupsSinceLastGC
++;
322 return aSuspectedBeforeForgetSkippable
- aSuspectedCCObjects
;
325 // Test if we are in the NoteCCBegin .. NoteCCEnd interval.
326 bool IsCollectingCycles() const { return mIsCollectingCycles
; }
328 // The CC was abandoned without running a slice, so we only did forget
329 // skippables. Prevent running another cycle soon.
330 void NoteForgetSkippableOnlyCycle(TimeStamp aNow
) {
331 mLastForgetSkippableCycleEndTime
= aNow
;
336 KillAllTimersAndRunners();
341 // Return a budget along with a boolean saying whether to prefer to run short
342 // slices and stop rather than continuing to the next phase of cycle
344 js::SliceBudget
ComputeCCSliceBudget(TimeStamp aDeadline
,
345 TimeStamp aCCBeginTime
,
346 TimeStamp aPrevSliceEndTime
,
348 bool* aPreferShorterSlices
) const;
350 js::SliceBudget
ComputeInterSliceGCBudget(TimeStamp aDeadline
,
353 bool ShouldForgetSkippable(uint32_t aSuspectedCCObjects
) const {
354 // Only do a forget skippable if there are more than a few new objects
355 // or we're doing the initial forget skippables.
356 return ((mPreviousSuspectedCount
+ 100) <= aSuspectedCCObjects
) ||
357 mCleanupsSinceLastGC
< kMajorForgetSkippableCalls
;
360 // There is reason to suspect that there may be a significant amount of
361 // garbage to cycle collect: either we just finished a GC, or the purple
362 // buffer is getting really big, or it's getting somewhat big and it has been
363 // too long since the last CC.
364 CCReason
IsCCNeeded(TimeStamp aNow
, uint32_t aSuspectedCCObjects
) const {
365 if (mNeedsFullCC
!= CCReason::NO_REASON
) {
368 if (aSuspectedCCObjects
> kCCPurpleLimit
) {
369 return CCReason::MANY_SUSPECTED
;
371 if (aSuspectedCCObjects
> kCCForcedPurpleLimit
&& mLastCCEndTime
&&
372 aNow
- mLastCCEndTime
> kCCForced
) {
373 return CCReason::TIMED
;
375 return CCReason::NO_REASON
;
378 mozilla::CCReason
ShouldScheduleCC(TimeStamp aNow
,
379 uint32_t aSuspectedCCObjects
) const;
381 // If we collected a substantial amount of cycles, poke the GC since more
382 // objects might be unreachable now.
383 bool NeedsGCAfterCC() const {
384 return mCCollectedWaitingForGC
> 250 || mCCollectedZonesWaitingForGC
> 0 ||
385 mLikelyShortLivingObjectsNeedingGC
> 2500 || mNeedsGCAfterCC
;
388 bool IsLastEarlyCCTimer(int32_t aCurrentFireCount
) const {
389 int32_t numEarlyTimerFires
=
390 std::max(int32_t(mCCDelay
/ kCCSkippableDelay
) - 2, 1);
392 return aCurrentFireCount
>= numEarlyTimerFires
;
395 enum class CCRunnerState
{
399 CleanupContentUnbinder
,
401 StartCycleCollection
,
407 void InitCCRunnerStateMachine(CCRunnerState initialState
, CCReason aReason
) {
412 MOZ_ASSERT(mCCReason
== CCReason::NO_REASON
);
415 // The state machine should always have been deactivated after the previous
416 // collection, however far that collection may have gone.
417 MOZ_ASSERT(mCCRunnerState
== CCRunnerState::Inactive
,
418 "DeactivateCCRunner should have been called");
419 mCCRunnerState
= initialState
;
421 // Currently, there are only two entry points to the non-Inactive part of
422 // the state machine.
423 if (initialState
== CCRunnerState::ReducePurple
) {
425 mCCRunnerEarlyFireCount
= 0;
426 } else if (initialState
== CCRunnerState::CycleCollecting
) {
429 MOZ_CRASH("Invalid initial state");
433 void DeactivateCCRunner() {
434 mCCRunnerState
= CCRunnerState::Inactive
;
435 mCCReason
= CCReason::NO_REASON
;
438 bool HasMoreIdleGCRunnerWork() const {
439 return mMajorGCReason
!= JS::GCReason::NO_REASON
||
440 mEagerMajorGCReason
!= JS::GCReason::NO_REASON
||
441 mEagerMinorGCReason
!= JS::GCReason::NO_REASON
;
444 GCRunnerStep
GetNextGCRunnerAction(TimeStamp aDeadline
) const;
446 CCRunnerStep
AdvanceCCRunner(TimeStamp aDeadline
, TimeStamp aNow
,
447 uint32_t aSuspectedCCObjects
);
449 // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some
450 // time ago, if an incremental GC needed to be finished.
451 js::SliceBudget
ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp
,
452 TimeStamp aDeadline
);
457 // An incremental GC is in progress, which blocks the CC from running for its
458 // duration (or until it goes too long and is finished synchronously.)
459 bool mInIncrementalGC
= false;
461 // Whether to ask the parent process if now is a good time to GC (false for
462 // the parent process.)
463 const bool mAskParentBeforeMajorGC
;
465 // We've asked the parent process if now is a good time to GC (do not ask
467 bool mHaveAskedParent
= false;
469 // The parent process is ready for us to do a major GC.
470 bool mReadyForMajorGC
;
472 // Set when the IdleTaskRunner requests the current task be interrupted.
473 // Cleared when the GC slice budget has detected the interrupt request.
474 js::SliceBudget::InterruptRequestFlag mInterruptRequested
;
476 // When a shrinking GC has been requested but we back-out, if this is true
477 // we run a non-shrinking GC.
478 bool mWantAtLeastRegularGC
= false;
480 // When the CC started actually waiting for the GC to finish. This will be
481 // set to non-null at a later time than mCCLockedOut.
482 TimeStamp mCCBlockStart
;
484 bool mDidShutdown
= false;
486 TimeStamp mLastForgetSkippableEndTime
;
487 uint32_t mForgetSkippableCounter
= 0;
488 TimeStamp mForgetSkippableFrequencyStartTime
;
489 TimeStamp mLastCCEndTime
;
490 TimeStamp mLastForgetSkippableCycleEndTime
;
492 CCRunnerState mCCRunnerState
= CCRunnerState::Inactive
;
493 int32_t mCCRunnerEarlyFireCount
= 0;
494 TimeDuration mCCDelay
= kCCDelay
;
496 // Prevent the very first CC from running before we have GC'd and set the
498 bool mHasRunGC
= false;
500 mozilla::CCReason mNeedsFullCC
= CCReason::NO_REASON
;
501 bool mNeedsFullGC
= true;
502 bool mNeedsGCAfterCC
= false;
503 uint32_t mPreviousSuspectedCount
= 0;
505 uint32_t mCleanupsSinceLastGC
= UINT32_MAX
;
507 // If the GC runner triggers a GC slice, this will be set to the idle deadline
508 // or the null timestamp if non-idle. It will be Nothing at the end of an
509 // internally-triggered slice.
510 mozilla::Maybe
<TimeStamp
> mTriggeredGCDeadline
;
512 RefPtr
<IdleTaskRunner
> mGCRunner
;
513 RefPtr
<IdleTaskRunner
> mCCRunner
;
514 nsITimer
* mShrinkingGCTimer
= nullptr;
515 nsITimer
* mFullGCTimer
= nullptr;
517 mozilla::CCReason mCCReason
= mozilla::CCReason::NO_REASON
;
518 JS::GCReason mMajorGCReason
= JS::GCReason::NO_REASON
;
519 JS::GCReason mEagerMajorGCReason
= JS::GCReason::NO_REASON
;
520 JS::GCReason mEagerMinorGCReason
= JS::GCReason::NO_REASON
;
522 bool mIsCompactingOnUserInactive
= false;
523 bool mIsCollectingCycles
= false;
524 bool mUserIsActive
= true;
527 uint32_t mCCollectedWaitingForGC
= 0;
528 uint32_t mCCollectedZonesWaitingForGC
= 0;
529 uint32_t mLikelyShortLivingObjectsNeedingGC
= 0;
531 // Configuration parameters
533 TimeDuration mActiveIntersliceGCBudget
= TimeDuration::FromMilliseconds(5);
536 } // namespace mozilla