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 #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 16 namespace mozilla { 17 18 static const TimeDuration kOneMinute = TimeDuration::FromSeconds(60.0f); 19 20 // The amount of time we wait between a request to CC (after GC ran) 21 // and doing the actual CC. 22 static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6); 23 24 static const TimeDuration kCCSkippableDelay = 25 TimeDuration::FromMilliseconds(250); 26 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 static const TimeDuration kTimeBetweenForgetSkippableCycles = 31 TimeDuration::FromSeconds(2); 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 static const TimeDuration kForgetSkippableSliceDuration = 37 TimeDuration::FromMilliseconds(2); 38 39 // Maximum amount of time that should elapse between incremental CC slices 40 static const TimeDuration kICCIntersliceDelay = 41 TimeDuration::FromMilliseconds(64); 42 43 // Time budget for an incremental CC slice when using timer to run it. 44 static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3); 45 // Minimum budget for an incremental CC slice when using idle time to run it. 46 static const TimeDuration kIdleICCSliceBudget = 47 TimeDuration::FromMilliseconds(2); 48 49 // Maximum total duration for an ICC 50 static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2); 51 52 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT 53 // objects in the purple buffer. 54 static const TimeDuration kCCForced = kOneMinute * 2; 55 static const uint32_t kCCForcedPurpleLimit = 10; 56 57 // Don't allow an incremental GC to lock out the CC for too long. 58 static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30); 59 60 // Trigger a CC if the purple buffer exceeds this size when we check it. 61 static const uint32_t kCCPurpleLimit = 200; 62 63 // How many cycle collected nodes to traverse between time checks. 64 static const int64_t kNumCCNodesBetweenTimeChecks = 1000; 65 66 // Actions performed by the GCRunner state machine. 67 enum class GCRunnerAction { 68 WaitToMajorGC, // We want to start a new major GC 69 StartMajorGC, // The parent says we may begin our major GC 70 GCSlice, // Run a single slice of a major GC 71 None 72 }; 73 74 struct GCRunnerStep { 75 GCRunnerAction mAction; 76 JS::GCReason mReason; 77 }; 78 79 enum class CCRunnerAction { 80 None, 81 ForgetSkippable, 82 CleanupContentUnbinder, 83 CleanupDeferred, 84 CycleCollect, 85 StopRunning 86 }; 87 88 enum CCRunnerYield { Continue, Yield }; 89 90 enum CCRunnerForgetSkippableRemoveChildless { 91 KeepChildless = false, 92 RemoveChildless = true 93 }; 94 95 struct CCRunnerStep { 96 // The action to scheduler is instructing the caller to perform. 97 CCRunnerAction mAction; 98 99 // Whether to stop processing actions for this invocation of the timer 100 // callback. 101 CCRunnerYield mYield; 102 103 // If the action is ForgetSkippable, then whether to remove childless nodes 104 // or not. (ForgetSkippable is the only action requiring a parameter; if 105 // that changes, this will become a union.) 106 CCRunnerForgetSkippableRemoveChildless mRemoveChildless; 107 }; 108 109 class CCGCScheduler { 110 public: 111 static bool CCRunnerFired(TimeStamp aDeadline); 112 113 // Parameter setting 114 SetActiveIntersliceGCBudget(TimeDuration aDuration)115 void SetActiveIntersliceGCBudget(TimeDuration aDuration) { 116 mActiveIntersliceGCBudget = aDuration; 117 } 118 119 // State retrieval 120 GetCCBlockedTime(TimeStamp aNow)121 TimeDuration GetCCBlockedTime(TimeStamp aNow) const { 122 MOZ_ASSERT(mInIncrementalGC); 123 MOZ_ASSERT(!mCCBlockStart.IsNull()); 124 return aNow - mCCBlockStart; 125 } 126 InIncrementalGC()127 bool InIncrementalGC() const { return mInIncrementalGC; } 128 GetLastCCEndTime()129 TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; } 130 131 bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const { 132 return mCleanupsSinceLastGC < aN; 133 } 134 NeedsFullGC()135 bool NeedsFullGC() const { return mNeedsFullGC; } 136 137 // Requests 138 void PokeGC(JS::GCReason aReason, JSObject* aObj, uint32_t aDelay = 0); 139 void PokeShrinkingGC(); 140 void PokeFullGC(); 141 void MaybePokeCC(TimeStamp aNow, uint32_t aSuspectedCCObjects); 142 143 void UserIsInactive(); 144 void UserIsActive(); 145 146 void KillShrinkingGCTimer(); 147 void KillFullGCTimer(); 148 void KillGCRunner(); 149 void KillCCRunner(); 150 void KillAllTimersAndRunners(); 151 152 /* 153 * aDelay is the delay before the first time the idle task runner runs. 154 * Then it runs every 155 * StaticPrefs::javascript_options_gc_delay_interslice() 156 */ 157 void EnsureGCRunner(uint32_t aDelay); 158 159 void EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget); 160 161 // State modification 162 163 void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; } 164 SetWantMajorGC(JS::GCReason aReason)165 void SetWantMajorGC(JS::GCReason aReason) { 166 mMajorGCReason = aReason; 167 168 // Force full GCs when called from reftests so that we collect dead zones 169 // that have not been scheduled for collection. 170 if (aReason == JS::GCReason::DOM_WINDOW_UTILS) { 171 SetNeedsFullGC(); 172 } 173 } 174 // Ensure that the current runner does a cycle collection, and trigger a GC 175 // after it finishes. EnsureCCThenGC()176 void EnsureCCThenGC() { 177 MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive); 178 mNeedsFullCC = true; 179 mNeedsGCAfterCC = true; 180 } 181 182 // Returns false if we started and finished a major GC while waiting for a 183 // response. NoteReadyForMajorGC()184 [[nodiscard]] bool NoteReadyForMajorGC() { 185 if (mMajorGCReason == JS::GCReason::NO_REASON) { 186 return false; 187 } 188 mReadyForMajorGC = true; 189 return true; 190 } 191 NoteGCBegin()192 void NoteGCBegin() { 193 // Treat all GC as incremental here; non-incremental GC will just appear to 194 // be one slice. 195 mInIncrementalGC = true; 196 mReadyForMajorGC = false; 197 } 198 NoteGCEnd()199 void NoteGCEnd() { 200 mMajorGCReason = JS::GCReason::NO_REASON; 201 202 mInIncrementalGC = false; 203 mCCBlockStart = TimeStamp(); 204 mInIncrementalGC = false; 205 mReadyForMajorGC = false; 206 mNeedsFullCC = true; 207 mHasRunGC = true; 208 mIsCompactingOnUserInactive = false; 209 210 mCleanupsSinceLastGC = 0; 211 mCCollectedWaitingForGC = 0; 212 mCCollectedZonesWaitingForGC = 0; 213 mLikelyShortLivingObjectsNeedingGC = 0; 214 } 215 NoteGCSliceEnd(TimeDuration aSliceDuration)216 void NoteGCSliceEnd(TimeDuration aSliceDuration) { 217 if (mMajorGCReason == JS::GCReason::NO_REASON) { 218 // Internally-triggered GCs do not wait for the parent's permission to 219 // proceed. This flag won't be checked during an incremental GC anyway, 220 // but it better reflects reality. 221 mReadyForMajorGC = true; 222 } 223 224 // Subsequent slices should be INTER_SLICE_GC unless they are triggered by 225 // something else that provides its own reason. 226 mMajorGCReason = JS::GCReason::INTER_SLICE_GC; 227 228 mGCUnnotifiedTotalTime += aSliceDuration; 229 } 230 231 void FullGCTimerFired(nsITimer* aTimer); 232 void ShrinkingGCTimerFired(nsITimer* aTimer); 233 bool GCRunnerFired(TimeStamp aDeadline); 234 235 using MayGCPromise = 236 MozPromise<bool /* aIgnored */, mozilla::ipc::ResponseRejectReason, true>; 237 238 // Returns null if we shouldn't GC now (eg a GC is already running). 239 static RefPtr<MayGCPromise> MayGCNow(JS::GCReason reason); 240 241 // Check all of the various collector timers/runners and see if they are 242 // waiting to fire. This does not check the Full GC Timer, as that's a 243 // more expensive collection we run on a long timer. 244 void RunNextCollectorTimer(JS::GCReason aReason, 245 mozilla::TimeStamp aDeadline); 246 247 // When we decide to do a cycle collection but we're in the middle of an 248 // incremental GC, the CC is "locked out" until the GC completes -- unless 249 // the wait is too long, and we decide to finish the incremental GC early. BlockCC(TimeStamp aNow)250 void BlockCC(TimeStamp aNow) { 251 MOZ_ASSERT(mInIncrementalGC); 252 MOZ_ASSERT(mCCBlockStart.IsNull()); 253 mCCBlockStart = aNow; 254 } 255 UnblockCC()256 void UnblockCC() { mCCBlockStart = TimeStamp(); } 257 258 // Returns the number of purple buffer items that were processed and removed. NoteForgetSkippableComplete(TimeStamp aNow,uint32_t aSuspectedBeforeForgetSkippable,uint32_t aSuspectedCCObjects)259 uint32_t NoteForgetSkippableComplete(TimeStamp aNow, 260 uint32_t aSuspectedBeforeForgetSkippable, 261 uint32_t aSuspectedCCObjects) { 262 mLastForgetSkippableEndTime = aNow; 263 mPreviousSuspectedCount = aSuspectedCCObjects; 264 mCleanupsSinceLastGC++; 265 return aSuspectedBeforeForgetSkippable - aSuspectedCCObjects; 266 } 267 268 // After collecting cycles, record the results that are used in scheduling 269 // decisions. NoteCycleCollected(const CycleCollectorResults & aResults)270 void NoteCycleCollected(const CycleCollectorResults& aResults) { 271 mCCollectedWaitingForGC += aResults.mFreedGCed; 272 mCCollectedZonesWaitingForGC += aResults.mFreedJSZones; 273 } 274 275 // This is invoked when the whole process of collection is done -- i.e., CC 276 // preparation (eg ForgetSkippables), the CC itself, and the optional 277 // followup GC. There really ought to be a separate name for the overall CC 278 // as opposed to the actual cycle collection portion. NoteCCEnd(TimeStamp aWhen)279 void NoteCCEnd(TimeStamp aWhen) { 280 mLastCCEndTime = aWhen; 281 mNeedsFullCC = false; 282 283 // The GC for this CC has already been requested. 284 mNeedsGCAfterCC = false; 285 } 286 287 // The CC was abandoned without running a slice, so we only did forget 288 // skippables. Prevent running another cycle soon. NoteForgetSkippableOnlyCycle(TimeStamp aNow)289 void NoteForgetSkippableOnlyCycle(TimeStamp aNow) { 290 mLastForgetSkippableCycleEndTime = aNow; 291 } 292 Shutdown()293 void Shutdown() { 294 mDidShutdown = true; 295 KillAllTimersAndRunners(); 296 } 297 298 // Scheduling 299 300 // Return a budget along with a boolean saying whether to prefer to run short 301 // slices and stop rather than continuing to the next phase of cycle 302 // collection. 303 js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline, 304 TimeStamp aCCBeginTime, 305 TimeStamp aPrevSliceEndTime, 306 TimeStamp aNow, 307 bool* aPreferShorterSlices) const; 308 309 TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline, 310 TimeStamp aNow) const; 311 ShouldForgetSkippable(uint32_t aSuspectedCCObjects)312 bool ShouldForgetSkippable(uint32_t aSuspectedCCObjects) const { 313 // Only do a forget skippable if there are more than a few new objects 314 // or we're doing the initial forget skippables. 315 return ((mPreviousSuspectedCount + 100) <= aSuspectedCCObjects) || 316 mCleanupsSinceLastGC < kMajorForgetSkippableCalls; 317 } 318 319 // There is reason to suspect that there may be a significant amount of 320 // garbage to cycle collect: either we just finished a GC, or the purple 321 // buffer is getting really big, or it's getting somewhat big and it has been 322 // too long since the last CC. IsCCNeeded(TimeStamp aNow,uint32_t aSuspectedCCObjects)323 bool IsCCNeeded(TimeStamp aNow, uint32_t aSuspectedCCObjects) const { 324 if (mNeedsFullCC) { 325 return true; 326 } 327 return aSuspectedCCObjects > kCCPurpleLimit || 328 (aSuspectedCCObjects > kCCForcedPurpleLimit && mLastCCEndTime && 329 aNow - mLastCCEndTime > kCCForced); 330 } 331 332 bool ShouldScheduleCC(TimeStamp aNow, uint32_t aSuspectedCCObjects) const; 333 334 // If we collected a substantial amount of cycles, poke the GC since more 335 // objects might be unreachable now. NeedsGCAfterCC()336 bool NeedsGCAfterCC() const { 337 return mCCollectedWaitingForGC > 250 || mCCollectedZonesWaitingForGC > 0 || 338 mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC; 339 } 340 IsLastEarlyCCTimer(int32_t aCurrentFireCount)341 bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const { 342 int32_t numEarlyTimerFires = 343 std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1); 344 345 return aCurrentFireCount >= numEarlyTimerFires; 346 } 347 348 enum class CCRunnerState { 349 Inactive, 350 ReducePurple, 351 CleanupChildless, 352 CleanupContentUnbinder, 353 CleanupDeferred, 354 StartCycleCollection, 355 CycleCollecting, 356 Canceled, 357 NumStates 358 }; 359 InitCCRunnerStateMachine(CCRunnerState initialState)360 void InitCCRunnerStateMachine(CCRunnerState initialState) { 361 if (mCCRunner) { 362 return; 363 } 364 365 // The state machine should always have been deactivated after the previous 366 // collection, however far that collection may have gone. 367 MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive, 368 "DeactivateCCRunner should have been called"); 369 mCCRunnerState = initialState; 370 371 // Currently, there are only two entry points to the non-Inactive part of 372 // the state machine. 373 if (initialState == CCRunnerState::ReducePurple) { 374 mCCDelay = kCCDelay; 375 mCCRunnerEarlyFireCount = 0; 376 } else if (initialState == CCRunnerState::CycleCollecting) { 377 // Nothing needed. 378 } else { 379 MOZ_CRASH("Invalid initial state"); 380 } 381 } 382 DeactivateCCRunner()383 void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; } 384 385 GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline); 386 387 CCRunnerStep AdvanceCCRunner(TimeStamp aDeadline, TimeStamp aNow, 388 uint32_t aSuspectedCCObjects); 389 390 // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some 391 // time ago, if an incremental GC needed to be finished. 392 js::SliceBudget ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp, 393 TimeStamp aDeadline); 394 395 private: 396 // State 397 398 // An incremental GC is in progress, which blocks the CC from running for its 399 // duration (or until it goes too long and is finished synchronously.) 400 bool mInIncrementalGC = false; 401 402 // The parent process is ready for us to do a major GC. 403 bool mReadyForMajorGC = false; 404 405 // When the CC started actually waiting for the GC to finish. This will be 406 // set to non-null at a later time than mCCLockedOut. 407 TimeStamp mCCBlockStart; 408 409 bool mDidShutdown = false; 410 411 TimeStamp mLastForgetSkippableEndTime; 412 uint32_t mForgetSkippableCounter = 0; 413 TimeStamp mForgetSkippableFrequencyStartTime; 414 TimeStamp mLastCCEndTime; 415 TimeStamp mLastForgetSkippableCycleEndTime; 416 417 CCRunnerState mCCRunnerState = CCRunnerState::Inactive; 418 int32_t mCCRunnerEarlyFireCount = 0; 419 TimeDuration mCCDelay = kCCDelay; 420 421 // Prevent the very first CC from running before we have GC'd and set the 422 // gray bits. 423 bool mHasRunGC = false; 424 425 bool mNeedsFullCC = false; 426 bool mNeedsFullGC = true; 427 bool mNeedsGCAfterCC = false; 428 uint32_t mPreviousSuspectedCount = 0; 429 430 uint32_t mCleanupsSinceLastGC = UINT32_MAX; 431 432 TimeDuration mGCUnnotifiedTotalTime; 433 434 RefPtr<IdleTaskRunner> mGCRunner; 435 RefPtr<IdleTaskRunner> mCCRunner; 436 nsITimer* mShrinkingGCTimer = nullptr; 437 nsITimer* mFullGCTimer = nullptr; 438 439 JS::GCReason mMajorGCReason = JS::GCReason::NO_REASON; 440 441 bool mIsCompactingOnUserInactive = false; 442 bool mUserIsActive = true; 443 444 public: 445 uint32_t mCCollectedWaitingForGC = 0; 446 uint32_t mCCollectedZonesWaitingForGC = 0; 447 uint32_t mLikelyShortLivingObjectsNeedingGC = 0; 448 449 // Configuration parameters 450 451 TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5); 452 }; 453 454 } // namespace mozilla 455