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