CCGCScheduler.h (18385B)
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/. */ 4 5 #ifndef mozilla_dom_CCGCScheduler_h 6 #define mozilla_dom_CCGCScheduler_h 7 8 #include "js/SliceBudget.h" 9 #include "mozilla/CycleCollectedJSContext.h" 10 #include "mozilla/IdleTaskRunner.h" 11 #include "mozilla/MainThreadIdlePeriod.h" 12 #include "mozilla/TimeStamp.h" 13 #include "mozilla/ipc/IdleSchedulerChild.h" 14 #include "nsCycleCollectionParticipant.h" 15 #include "nsCycleCollector.h" 16 #include "nsJSEnvironment.h" 17 18 namespace mozilla { 19 20 extern const TimeDuration kOneMinute; 21 22 // The amount of time we wait between a request to CC (after GC ran) 23 // and doing the actual CC. 24 extern const TimeDuration kCCDelay; 25 26 extern const TimeDuration kCCSkippableDelay; 27 28 // In case the cycle collector isn't run at all, we don't want forget skippables 29 // to run too often. So limit the forget skippable cycle to start at earliest 2 30 // seconds after the end of the previous cycle. 31 extern const TimeDuration kTimeBetweenForgetSkippableCycles; 32 33 // ForgetSkippable is usually fast, so we can use small budgets. 34 // This isn't a real budget but a hint to IdleTaskRunner whether there 35 // is enough time to call ForgetSkippable. 36 extern const TimeDuration kForgetSkippableSliceDuration; 37 38 // Maximum amount of time that should elapse between incremental CC slices 39 extern const TimeDuration kICCIntersliceDelay; 40 41 // Time budget for an incremental CC slice when using timer to run it. 42 extern const TimeDuration kICCSliceBudget; 43 // Minimum budget for an incremental CC slice when using idle time to run it. 44 extern const TimeDuration kIdleICCSliceBudget; 45 46 // Maximum total duration for an ICC 47 extern const TimeDuration kMaxICCDuration; 48 49 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT 50 // objects in the purple buffer. 51 extern const TimeDuration kCCForced; 52 constexpr uint32_t kCCForcedPurpleLimit = 10; 53 54 // Don't allow an incremental GC to lock out the CC for too long. 55 extern const TimeDuration kMaxCCLockedoutTime; 56 57 // Trigger a CC if the purple buffer exceeds this size when we check it. 58 constexpr uint32_t kCCPurpleLimit = 200; 59 60 // Actions performed by the GCRunner state machine. 61 enum class GCRunnerAction { 62 MinorGC, // Run a minor GC (nursery collection) 63 WaitToMajorGC, // We want to start a new major GC 64 StartMajorGC, // The parent says we may begin our major GC 65 GCSlice, // Run a single slice of a major GC 66 None 67 }; 68 69 struct GCRunnerStep { 70 GCRunnerAction mAction; 71 JS::GCReason mReason; 72 }; 73 74 // Actions that are output from the CCRunner state machine. 75 enum class CCRunnerAction { 76 // Do nothing. 77 None, 78 79 // We crossed an eager minor GC threshold in the middle of an incremental CC, 80 // and we have some idle time. 81 MinorGC, 82 83 // Various cleanup actions. 84 ForgetSkippable, 85 CleanupContentUnbinder, 86 CleanupDeferred, 87 88 // Do the actual cycle collection (build the graph etc). 89 CycleCollect, 90 91 // All done. 92 StopRunning 93 }; 94 95 enum CCRunnerYield { Continue, Yield }; 96 97 enum CCRunnerForgetSkippableRemoveChildless { 98 KeepChildless = false, 99 RemoveChildless = true 100 }; 101 102 struct CCRunnerStep { 103 // The action the scheduler is instructing the caller to perform. 104 CCRunnerAction mAction; 105 106 // Whether to stop processing actions for this invocation of the timer 107 // callback. 108 CCRunnerYield mYield; 109 110 union ActionData { 111 // If the action is ForgetSkippable, then whether to remove childless nodes 112 // or not. 113 CCRunnerForgetSkippableRemoveChildless mRemoveChildless; 114 115 // If the action is CycleCollect, the reason for the collection. 116 CCReason mCCReason; 117 118 // If the action is MinorGC, the reason for the GC. 119 JS::GCReason mReason; 120 121 MOZ_IMPLICIT ActionData(CCRunnerForgetSkippableRemoveChildless v) 122 : mRemoveChildless(v) {} 123 MOZ_IMPLICIT ActionData(CCReason v) : mCCReason(v) {} 124 MOZ_IMPLICIT ActionData(JS::GCReason v) : mReason(v) {} 125 ActionData() = default; 126 } mParam; 127 }; 128 129 class CCGCScheduler { 130 public: 131 CCGCScheduler() 132 : mAskParentBeforeMajorGC(XRE_IsContentProcess()), 133 mReadyForMajorGC(!mAskParentBeforeMajorGC), 134 mInterruptRequested(false) {} 135 136 bool CCRunnerFired(TimeStamp aDeadline); 137 138 // Parameter setting 139 140 void SetActiveIntersliceGCBudget(TimeDuration aDuration) { 141 mActiveIntersliceGCBudget = aDuration; 142 } 143 144 // State retrieval 145 146 TimeDuration GetCCBlockedTime(TimeStamp aNow) const { 147 MOZ_ASSERT(mInIncrementalGC); 148 MOZ_ASSERT(!mCCBlockStart.IsNull()); 149 return aNow - mCCBlockStart; 150 } 151 152 bool InIncrementalGC() const { return mInIncrementalGC; } 153 154 TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; } 155 156 bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const { 157 return mCleanupsSinceLastGC < aN; 158 } 159 160 bool NeedsFullGC() const { return mNeedsFullGC; } 161 162 // Requests 163 void PokeGC(JS::GCReason aReason, JSObject* aObj, TimeDuration aDelay = 0); 164 void PokeShrinkingGC(); 165 void PokeFullGC(); 166 void MaybePokeCC(TimeStamp aNow, uint32_t aSuspectedCCObjects); 167 void PokeMinorGC(JS::GCReason aReason); 168 169 void UserIsInactive(); 170 void UserIsActive(); 171 bool IsUserActive() const { return mUserIsActive; } 172 173 void KillShrinkingGCTimer(); 174 void KillFullGCTimer(); 175 void KillGCRunner(); 176 void KillCCRunner(); 177 void KillAllTimersAndRunners(); 178 179 JS::SliceBudget CreateGCSliceBudget(mozilla::TimeDuration aDuration, 180 bool isIdle, bool isExtended) { 181 mInterruptRequested = false; 182 // Don't try to interrupt if we are in a mode where idle time is rare. 183 auto budget = JS::SliceBudget( 184 aDuration, mPreferFasterCollection ? nullptr : &mInterruptRequested); 185 budget.idle = isIdle; 186 budget.extended = isExtended; 187 return budget; 188 } 189 190 /* 191 * aDelay is the delay before the first time the idle task runner runs. 192 * Then it runs every 193 * StaticPrefs::javascript_options_gc_delay_interslice() 194 */ 195 void EnsureGCRunner(TimeDuration aDelay); 196 197 // If GCRunner isn't active, this calls EnsureGCRunner(0). Otherwise the timer 198 // is reset. 199 void EnsureOrResetGCRunner(); 200 201 void EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget); 202 203 // State modification 204 205 void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; } 206 207 void SetWantMajorGC(JS::GCReason aReason) { 208 MOZ_ASSERT(aReason != JS::GCReason::NO_REASON); 209 210 // If the GC being requested is not a shrinking GC set this flag. 211 // If/when the shrinking GC timer fires but the user is active we check 212 // this flag before canceling the GC, so as not to cancel the 213 // non-shrinking GC being requested here. 214 if (aReason != JS::GCReason::USER_INACTIVE) { 215 mWantAtLeastRegularGC = true; 216 } 217 218 // Force full GCs when called from reftests so that we collect dead zones 219 // that have not been scheduled for collection. 220 if (aReason == JS::GCReason::DOM_WINDOW_UTILS) { 221 SetNeedsFullGC(); 222 } 223 224 // USER_INACTIVE trumps everything, 225 // FULL_GC_TIMER trumps everything except USER_INACTIVE, 226 // all other reasons just use the latest reason. 227 switch (aReason) { 228 case JS::GCReason::USER_INACTIVE: 229 mMajorGCReason = aReason; 230 break; 231 case JS::GCReason::FULL_GC_TIMER: 232 if (mMajorGCReason != JS::GCReason::USER_INACTIVE) { 233 mMajorGCReason = aReason; 234 } 235 break; 236 default: 237 if (mMajorGCReason != JS::GCReason::USER_INACTIVE && 238 mMajorGCReason != JS::GCReason::FULL_GC_TIMER) { 239 mMajorGCReason = aReason; 240 } 241 break; 242 } 243 } 244 245 void SetWantEagerMinorGC(JS::GCReason aReason) { 246 if (mEagerMinorGCReason == JS::GCReason::NO_REASON) { 247 mEagerMinorGCReason = aReason; 248 } 249 } 250 251 // Ensure that the current runner does a cycle collection, and trigger a GC 252 // after it finishes. 253 void EnsureCCThenGC(CCReason aReason) { 254 MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive); 255 MOZ_ASSERT(aReason != CCReason::NO_REASON); 256 mNeedsFullCC = aReason; 257 mNeedsGCAfterCC = true; 258 } 259 260 // Returns false if we started and finished a major GC while waiting for a 261 // response. 262 [[nodiscard]] bool NoteReadyForMajorGC() { 263 if (mMajorGCReason == JS::GCReason::NO_REASON || InIncrementalGC()) { 264 return false; 265 } 266 mReadyForMajorGC = true; 267 return true; 268 } 269 270 // Starting a major GC (incremental or non-incremental). 271 void NoteGCBegin(JS::GCReason aReason); 272 273 // Major GC completed. 274 void NoteGCEnd(); 275 276 // A timer fired, but then decided not to run a GC. 277 void NoteWontGC(); 278 279 void NoteMinorGCEnd() { mEagerMinorGCReason = JS::GCReason::NO_REASON; } 280 281 // This is invoked when we reach the actual cycle collection portion of the 282 // overall cycle collection. 283 void NoteCCBegin(); 284 285 // This is invoked when the whole process of collection is done -- i.e., CC 286 // preparation (eg ForgetSkippables) in addition to the CC itself. There 287 // really ought to be a separate name for the overall CC as opposed to the 288 // actual cycle collection portion. 289 void NoteCCEnd(const CycleCollectorResults& aResults, TimeStamp aWhen); 290 291 // A single slice has completed. 292 void NoteGCSliceEnd(TimeStamp aStart, TimeStamp aEnd); 293 294 bool GCRunnerFired(TimeStamp aDeadline); 295 bool GCRunnerFiredDoGC(TimeStamp aDeadline, const GCRunnerStep& aStep); 296 297 using MayGCPromise = 298 MozPromise<bool, mozilla::ipc::ResponseRejectReason, true>; 299 300 // Returns null if we shouldn't GC now (eg a GC is already running). 301 static RefPtr<MayGCPromise> MayGCNow(JS::GCReason reason); 302 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); 308 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; 316 } 317 318 void UnblockCC() { mCCBlockStart = TimeStamp(); } 319 320 // Returns the number of purple buffer items that were processed and removed. 321 void NoteForgetSkippableComplete(TimeStamp aNow, 322 uint32_t aSuspectedCCObjects) { 323 mLastForgetSkippableEndTime = aNow; 324 mPreviousSuspectedCount = aSuspectedCCObjects; 325 mCleanupsSinceLastGC++; 326 } 327 328 // Test if we are in the NoteCCBegin .. NoteCCEnd interval. 329 bool IsCollectingCycles() const { return mIsCollectingCycles; } 330 331 // The CC was abandoned without running a slice, so we only did forget 332 // skippables. Prevent running another cycle soon. 333 void NoteForgetSkippableOnlyCycle(TimeStamp aNow) { 334 mLastForgetSkippableCycleEndTime = aNow; 335 } 336 337 void Shutdown() { 338 mDidShutdown = true; 339 KillAllTimersAndRunners(); 340 } 341 342 // Scheduling 343 344 // Return a budget along with a boolean saying whether to prefer to run short 345 // slices and stop rather than continuing to the next phase of cycle 346 // collection. 347 JS::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline, 348 TimeStamp aCCBeginTime, 349 TimeStamp aPrevSliceEndTime, 350 TimeStamp aNow, 351 bool* aPreferShorterSlices) const; 352 353 JS::SliceBudget ComputeInterSliceGCBudget(TimeStamp aDeadline, 354 TimeStamp aNow); 355 356 TimeDuration ComputeMinimumBudgetForRunner(TimeDuration aBaseValue); 357 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; 363 } 364 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; 372 } 373 if (aSuspectedCCObjects > kCCPurpleLimit) { 374 return CCReason::MANY_SUSPECTED; 375 } 376 if (aSuspectedCCObjects > kCCForcedPurpleLimit && mLastCCEndTime && 377 aNow - mLastCCEndTime > kCCForced) { 378 return CCReason::TIMED; 379 } 380 return CCReason::NO_REASON; 381 } 382 383 mozilla::CCReason ShouldScheduleCC(TimeStamp aNow, 384 uint32_t aSuspectedCCObjects) const; 385 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; 391 } 392 393 bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const { 394 int32_t numEarlyTimerFires = 395 std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1); 396 397 return aCurrentFireCount >= numEarlyTimerFires; 398 } 399 400 enum class CCRunnerState { 401 Inactive, 402 ReducePurple, 403 CleanupChildless, 404 CleanupContentUnbinder, 405 CleanupDeferred, 406 StartCycleCollection, 407 CycleCollecting, 408 Canceled, 409 NumStates 410 }; 411 412 void InitCCRunnerStateMachine(CCRunnerState initialState, CCReason aReason) { 413 if (mCCRunner) { 414 return; 415 } 416 417 MOZ_ASSERT(mCCReason == CCReason::NO_REASON); 418 mCCReason = aReason; 419 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; 425 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"); 435 } 436 } 437 438 void DeactivateCCRunner() { 439 mCCRunnerState = CCRunnerState::Inactive; 440 mCCReason = CCReason::NO_REASON; 441 } 442 443 bool HasMoreIdleGCRunnerWork() const { 444 return mMajorGCReason != JS::GCReason::NO_REASON || 445 mEagerMajorGCReason != JS::GCReason::NO_REASON || 446 mEagerMinorGCReason != JS::GCReason::NO_REASON; 447 } 448 449 GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline) const; 450 451 CCRunnerStep AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow, 452 uint32_t aSuspectedCCObjects); 453 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); 458 459 bool PreferFasterCollection() const { return mPreferFasterCollection; } 460 461 private: 462 // State 463 464 // An incremental GC is in progress, which blocks the CC from running for its 465 // duration (or until it goes too long and is finished synchronously.) 466 bool mInIncrementalGC = false; 467 468 // Whether to ask the parent process if now is a good time to GC (false for 469 // the parent process.) 470 const bool mAskParentBeforeMajorGC; 471 472 // We've asked the parent process if now is a good time to GC (do not ask 473 // again). 474 bool mHaveAskedParent = false; 475 476 // The parent process is ready for us to do a major GC. 477 bool mReadyForMajorGC; 478 479 // Set when the IdleTaskRunner requests the current task be interrupted. 480 // Cleared when the GC slice budget has detected the interrupt request. 481 JS::SliceBudget::InterruptRequestFlag mInterruptRequested; 482 483 // When a shrinking GC has been requested but we back-out, if this is true 484 // we run a non-shrinking GC. 485 bool mWantAtLeastRegularGC = false; 486 487 // When the CC started actually waiting for the GC to finish. This will be 488 // set to non-null at a later time than mCCLockedOut. 489 TimeStamp mCCBlockStart; 490 491 bool mDidShutdown = false; 492 493 TimeStamp mLastForgetSkippableEndTime; 494 uint32_t mForgetSkippableCounter = 0; 495 TimeStamp mForgetSkippableFrequencyStartTime; 496 TimeStamp mLastCCEndTime; 497 TimeStamp mLastForgetSkippableCycleEndTime; 498 499 CCRunnerState mCCRunnerState = CCRunnerState::Inactive; 500 int32_t mCCRunnerEarlyFireCount = 0; 501 TimeDuration mCCDelay = kCCDelay; 502 503 // Prevent the very first CC from running before we have GC'd and set the 504 // gray bits. 505 bool mHasRunGC = false; 506 507 mozilla::CCReason mNeedsFullCC = CCReason::NO_REASON; 508 bool mNeedsFullGC = true; 509 bool mNeedsGCAfterCC = false; 510 uint32_t mPreviousSuspectedCount = 0; 511 512 uint32_t mCleanupsSinceLastGC = UINT32_MAX; 513 514 // If the GC runner triggers a GC slice, this will be set to the idle deadline 515 // or the null timestamp if non-idle. It will be Nothing at the end of an 516 // internally-triggered slice. 517 mozilla::Maybe<TimeStamp> mTriggeredGCDeadline; 518 519 RefPtr<IdleTaskRunner> mGCRunner; 520 RefPtr<IdleTaskRunner> mCCRunner; 521 nsITimer* mShrinkingGCTimer = nullptr; 522 nsITimer* mFullGCTimer = nullptr; 523 524 mozilla::CCReason mCCReason = mozilla::CCReason::NO_REASON; 525 JS::GCReason mMajorGCReason = JS::GCReason::NO_REASON; 526 JS::GCReason mEagerMajorGCReason = JS::GCReason::NO_REASON; 527 JS::GCReason mEagerMinorGCReason = JS::GCReason::NO_REASON; 528 529 bool mIsCompactingOnUserInactive = false; 530 bool mIsCollectingCycles = false; 531 bool mUserIsActive = true; 532 533 bool mCurrentCollectionHasSeenNonIdle = false; 534 bool mPreferFasterCollection = false; 535 536 public: 537 uint32_t mCCollectedWaitingForGC = 0; 538 uint32_t mCCollectedZonesWaitingForGC = 0; 539 uint32_t mLikelyShortLivingObjectsNeedingGC = 0; 540 541 // Configuration parameters 542 543 TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5); 544 }; 545 546 } // namespace mozilla 547 548 #endif