tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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