зеркало из https://github.com/mozilla/gecko-dev.git
646 строки
23 KiB
C++
646 строки
23 KiB
C++
/* 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/MainThreadIdlePeriod.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "nsCycleCollector.h"
|
|
#include "nsJSEnvironment.h"
|
|
|
|
namespace mozilla {
|
|
|
|
static const TimeDuration kOneMinute = TimeDuration::FromSeconds(60.0f);
|
|
|
|
// The amount of time we wait between a request to CC (after GC ran)
|
|
// and doing the actual CC.
|
|
static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6);
|
|
|
|
static const TimeDuration kCCSkippableDelay =
|
|
TimeDuration::FromMilliseconds(250);
|
|
|
|
// 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.
|
|
static const TimeDuration kTimeBetweenForgetSkippableCycles =
|
|
TimeDuration::FromSeconds(2);
|
|
|
|
// 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.
|
|
static const TimeDuration kForgetSkippableSliceDuration =
|
|
TimeDuration::FromMilliseconds(2);
|
|
|
|
// Maximum amount of time that should elapse between incremental CC slices
|
|
static const TimeDuration kICCIntersliceDelay =
|
|
TimeDuration::FromMilliseconds(64);
|
|
|
|
// Time budget for an incremental CC slice when using timer to run it.
|
|
static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3);
|
|
// Minimum budget for an incremental CC slice when using idle time to run it.
|
|
static const TimeDuration kIdleICCSliceBudget =
|
|
TimeDuration::FromMilliseconds(2);
|
|
|
|
// Maximum total duration for an ICC
|
|
static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2);
|
|
|
|
// Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
|
|
// objects in the purple buffer.
|
|
static const TimeDuration kCCForced = kOneMinute * 2;
|
|
static const uint32_t kCCForcedPurpleLimit = 10;
|
|
|
|
// Don't allow an incremental GC to lock out the CC for too long.
|
|
static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30);
|
|
|
|
// Trigger a CC if the purple buffer exceeds this size when we check it.
|
|
static const uint32_t kCCPurpleLimit = 200;
|
|
|
|
enum class CCRunnerAction {
|
|
None,
|
|
ForgetSkippable,
|
|
CleanupContentUnbinder,
|
|
CleanupDeferred,
|
|
CycleCollect,
|
|
StopRunning
|
|
};
|
|
|
|
enum CCRunnerYield { Continue, Yield };
|
|
|
|
enum CCRunnerForgetSkippableRemoveChildless {
|
|
KeepChildless = false,
|
|
RemoveChildless = true
|
|
};
|
|
|
|
struct CCRunnerStep {
|
|
// The action to scheduler is instructing the caller to perform.
|
|
CCRunnerAction mAction;
|
|
|
|
// Whether to stop processing actions for this invocation of the timer
|
|
// callback.
|
|
CCRunnerYield mYield;
|
|
|
|
// If the action is ForgetSkippable, then whether to remove childless nodes
|
|
// or not. (ForgetSkippable is the only action requiring a parameter; if
|
|
// that changes, this will become a union.)
|
|
CCRunnerForgetSkippableRemoveChildless mRemoveChildless;
|
|
};
|
|
|
|
class CCGCScheduler {
|
|
public:
|
|
// Mockable functions to interface with the code being scheduled.
|
|
|
|
// Current time. In real usage, this will just return TimeStamp::Now(), but
|
|
// tests can reimplement it to return a value controlled by the test.
|
|
static inline TimeStamp Now();
|
|
|
|
// Number of entries in the purple buffer (those objects whose ref counts
|
|
// have been decremented since the previous CC, roughly), and are therefore
|
|
// "suspected" of being members of cyclic garbage.
|
|
static inline uint32_t SuspectedCCObjects();
|
|
|
|
// 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; }
|
|
|
|
// State modification
|
|
|
|
void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; }
|
|
|
|
// Ensure that the current runner does a cycle collection, and trigger a GC
|
|
// after it finishes.
|
|
void EnsureCCThenGC() {
|
|
MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
|
|
mNeedsFullCC = true;
|
|
mNeedsGCAfterCC = true;
|
|
}
|
|
|
|
void NoteGCBegin() {
|
|
// Treat all GC as incremental here; non-incremental GC will just appear to
|
|
// be one slice.
|
|
mInIncrementalGC = true;
|
|
}
|
|
|
|
void NoteGCEnd() {
|
|
mInIncrementalGC = false;
|
|
mCCBlockStart = TimeStamp();
|
|
mInIncrementalGC = false;
|
|
mNeedsFullCC = true;
|
|
mHasRunGC = true;
|
|
|
|
mCleanupsSinceLastGC = 0;
|
|
mCCollectedWaitingForGC = 0;
|
|
mCCollectedZonesWaitingForGC = 0;
|
|
mLikelyShortLivingObjectsNeedingGC = 0;
|
|
}
|
|
|
|
// 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.
|
|
uint32_t NoteForgetSkippableComplete(
|
|
TimeStamp aNow, uint32_t aSuspectedBeforeForgetSkippable) {
|
|
mLastForgetSkippableEndTime = aNow;
|
|
uint32_t suspected = SuspectedCCObjects();
|
|
mPreviousSuspectedCount = suspected;
|
|
mCleanupsSinceLastGC++;
|
|
return aSuspectedBeforeForgetSkippable - suspected;
|
|
}
|
|
|
|
// After collecting cycles, record the results that are used in scheduling
|
|
// decisions.
|
|
void NoteCycleCollected(const CycleCollectorResults& aResults) {
|
|
mCCollectedWaitingForGC += aResults.mFreedGCed;
|
|
mCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
|
|
}
|
|
|
|
// This is invoked when the whole process of collection is done -- i.e., CC
|
|
// preparation (eg ForgetSkippables), the CC itself, and the optional
|
|
// followup GC. There really ought to be a separate name for the overall CC
|
|
// as opposed to the actual cycle collection portion.
|
|
void NoteCCEnd(TimeStamp aWhen) {
|
|
mLastCCEndTime = aWhen;
|
|
mNeedsFullCC = false;
|
|
|
|
// The GC for this CC has already been requested.
|
|
mNeedsGCAfterCC = false;
|
|
}
|
|
|
|
// The CC was abandoned without running a slice, so we only did forget
|
|
// skippables. Prevent running another cycle soon.
|
|
void NoteForgetSkippableOnlyCycle() {
|
|
mLastForgetSkippableCycleEndTime = Now();
|
|
}
|
|
|
|
void Shutdown() { mDidShutdown = true; }
|
|
|
|
// 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.
|
|
inline js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
|
|
TimeStamp aCCBeginTime,
|
|
TimeStamp aPrevSliceEndTime,
|
|
bool* aPreferShorterSlices) const;
|
|
|
|
inline TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline,
|
|
TimeStamp aNow) const;
|
|
|
|
bool ShouldForgetSkippable() 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) <= SuspectedCCObjects()) ||
|
|
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.
|
|
bool IsCCNeeded(TimeStamp aNow = Now()) const {
|
|
if (mNeedsFullCC) {
|
|
return true;
|
|
}
|
|
uint32_t suspected = SuspectedCCObjects();
|
|
return suspected > kCCPurpleLimit ||
|
|
(suspected > kCCForcedPurpleLimit && mLastCCEndTime &&
|
|
aNow - mLastCCEndTime > kCCForced);
|
|
}
|
|
|
|
inline bool ShouldScheduleCC() 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) {
|
|
// 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; }
|
|
|
|
inline CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline);
|
|
|
|
// 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);
|
|
|
|
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;
|
|
|
|
// 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;
|
|
|
|
bool mNeedsFullCC = false;
|
|
bool mNeedsFullGC = true;
|
|
bool mNeedsGCAfterCC = false;
|
|
uint32_t mPreviousSuspectedCount = 0;
|
|
|
|
uint32_t mCleanupsSinceLastGC = UINT32_MAX;
|
|
|
|
public:
|
|
uint32_t mCCollectedWaitingForGC = 0;
|
|
uint32_t mCCollectedZonesWaitingForGC = 0;
|
|
uint32_t mLikelyShortLivingObjectsNeedingGC = 0;
|
|
|
|
// Configuration parameters
|
|
|
|
TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);
|
|
};
|
|
|
|
js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
|
|
TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime,
|
|
bool* aPreferShorterSlices) const {
|
|
TimeStamp now = Now();
|
|
|
|
*aPreferShorterSlices =
|
|
aDeadline.IsNull() || (aDeadline - now) < kICCSliceBudget;
|
|
|
|
TimeDuration baseBudget =
|
|
aDeadline.IsNull() ? kICCSliceBudget : aDeadline - now;
|
|
|
|
if (aCCBeginTime.IsNull()) {
|
|
// If no CC is in progress, use the standard slice time.
|
|
return js::SliceBudget(baseBudget);
|
|
}
|
|
|
|
// Only run a limited slice if we're within the max running time.
|
|
MOZ_ASSERT(now >= aCCBeginTime);
|
|
TimeDuration runningTime = now - aCCBeginTime;
|
|
if (runningTime >= kMaxICCDuration) {
|
|
return js::SliceBudget::unlimited();
|
|
}
|
|
|
|
const TimeDuration maxSlice =
|
|
TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod());
|
|
|
|
// Try to make up for a delay in running this slice.
|
|
MOZ_ASSERT(now >= aPrevSliceEndTime);
|
|
double sliceDelayMultiplier = (now - aPrevSliceEndTime) / kICCIntersliceDelay;
|
|
TimeDuration delaySliceBudget =
|
|
std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice);
|
|
|
|
// Increase slice budgets up to |maxSlice| as we approach
|
|
// half way through the ICC, to avoid large sync CCs.
|
|
double percentToHalfDone =
|
|
std::min(2.0 * (runningTime / kMaxICCDuration), 1.0);
|
|
TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone);
|
|
|
|
// Note: We may have already overshot the deadline, in which case
|
|
// baseBudget will be negative and we will end up returning
|
|
// laterSliceBudget.
|
|
return js::SliceBudget(
|
|
std::max({delaySliceBudget, laterSliceBudget, baseBudget}));
|
|
}
|
|
|
|
inline TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(
|
|
TimeStamp aDeadline, TimeStamp aNow) const {
|
|
// We use longer budgets when the CC has been locked out but the CC has
|
|
// tried to run since that means we may have a significant amount of
|
|
// garbage to collect and it's better to GC in several longer slices than
|
|
// in a very long one.
|
|
TimeDuration budget =
|
|
aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2 : aDeadline - aNow;
|
|
if (!mCCBlockStart) {
|
|
return budget;
|
|
}
|
|
|
|
TimeDuration blockedTime = aNow - mCCBlockStart;
|
|
TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
|
|
double percentOfBlockedTime =
|
|
std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
|
|
return std::max(budget, maxSliceGCBudget.MultDouble(percentOfBlockedTime));
|
|
}
|
|
|
|
bool CCGCScheduler::ShouldScheduleCC() const {
|
|
if (!mHasRunGC) {
|
|
return false;
|
|
}
|
|
|
|
TimeStamp now = Now();
|
|
|
|
// Don't run consecutive CCs too often.
|
|
if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
|
|
if (now - mLastCCEndTime < kCCDelay) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If GC hasn't run recently and forget skippable only cycle was run,
|
|
// don't start a new cycle too soon.
|
|
if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
|
|
!mLastForgetSkippableCycleEndTime.IsNull()) {
|
|
if (now - mLastForgetSkippableCycleEndTime <
|
|
kTimeBetweenForgetSkippableCycles) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return IsCCNeeded(now);
|
|
}
|
|
|
|
CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline) {
|
|
struct StateDescriptor {
|
|
// When in this state, should we first check to see if we still have
|
|
// enough reason to CC?
|
|
bool mCanAbortCC;
|
|
|
|
// If we do decide to abort the CC, should we still try to forget
|
|
// skippables one more time?
|
|
bool mTryFinalForgetSkippable;
|
|
};
|
|
|
|
// The state descriptors for Inactive and Canceled will never actually be
|
|
// used. We will never call this function while Inactive, and Canceled is
|
|
// handled specially at the beginning.
|
|
constexpr StateDescriptor stateDescriptors[] = {
|
|
{false, false}, /* CCRunnerState::Inactive */
|
|
{false, false}, /* CCRunnerState::ReducePurple */
|
|
{true, true}, /* CCRunnerState::CleanupChildless */
|
|
{true, false}, /* CCRunnerState::CleanupContentUnbinder */
|
|
{false, false}, /* CCRunnerState::CleanupDeferred */
|
|
{false, false}, /* CCRunnerState::StartCycleCollection */
|
|
{false, false}, /* CCRunnerState::CycleCollecting */
|
|
{false, false}}; /* CCRunnerState::Canceled */
|
|
static_assert(
|
|
ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates),
|
|
"need one state descriptor per state");
|
|
const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)];
|
|
|
|
// Make sure we initialized the state machine.
|
|
MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
|
|
|
|
if (mDidShutdown) {
|
|
return {CCRunnerAction::StopRunning, Yield};
|
|
}
|
|
|
|
if (mCCRunnerState == CCRunnerState::Canceled) {
|
|
// When we cancel a cycle, there may have been a final ForgetSkippable.
|
|
return {CCRunnerAction::StopRunning, Yield};
|
|
}
|
|
|
|
TimeStamp now = Now();
|
|
|
|
if (InIncrementalGC()) {
|
|
if (mCCBlockStart.IsNull()) {
|
|
BlockCC(now);
|
|
|
|
// If we have reached the CycleCollecting state, then ignore CC timer
|
|
// fires while incremental GC is running. (Running ICC during an IGC
|
|
// would cause us to synchronously finish the GC, which is bad.)
|
|
//
|
|
// If we have not yet started cycle collecting, then reset our state so
|
|
// that we run forgetSkippable often enough before CC. Because of reduced
|
|
// mCCDelay, forgetSkippable will be called just a few times.
|
|
//
|
|
// The kMaxCCLockedoutTime limit guarantees that we end up calling
|
|
// forgetSkippable and CycleCollectNow eventually.
|
|
|
|
if (mCCRunnerState != CCRunnerState::CycleCollecting) {
|
|
mCCRunnerState = CCRunnerState::ReducePurple;
|
|
mCCRunnerEarlyFireCount = 0;
|
|
mCCDelay = kCCDelay / int64_t(3);
|
|
}
|
|
return {CCRunnerAction::None, Yield};
|
|
}
|
|
|
|
if (GetCCBlockedTime(now) < kMaxCCLockedoutTime) {
|
|
return {CCRunnerAction::None, Yield};
|
|
}
|
|
|
|
// Locked out for too long, so proceed and finish the incremental GC
|
|
// synchronously.
|
|
}
|
|
|
|
// For states that aren't just continuations of previous states, check
|
|
// whether a CC is still needed (after doing various things to reduce the
|
|
// purple buffer).
|
|
if (desc.mCanAbortCC && !IsCCNeeded(now)) {
|
|
// If we don't pass the threshold for wanting to cycle collect, stop now
|
|
// (after possibly doing a final ForgetSkippable).
|
|
mCCRunnerState = CCRunnerState::Canceled;
|
|
NoteForgetSkippableOnlyCycle();
|
|
|
|
// Preserve the previous code's idea of when to check whether a
|
|
// ForgetSkippable should be fired.
|
|
if (desc.mTryFinalForgetSkippable && ShouldForgetSkippable()) {
|
|
// The Canceled state will make us StopRunning after this action is
|
|
// performed (see conditional at top of function).
|
|
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
|
}
|
|
|
|
return {CCRunnerAction::StopRunning, Yield};
|
|
}
|
|
|
|
switch (mCCRunnerState) {
|
|
// ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait
|
|
// for some amount of time (kCCDelay, or less if incremental GC blocked
|
|
// this CC) while firing regular ForgetSkippable actions before continuing
|
|
// on.
|
|
case CCRunnerState::ReducePurple:
|
|
++mCCRunnerEarlyFireCount;
|
|
if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) {
|
|
mCCRunnerState = CCRunnerState::CleanupChildless;
|
|
}
|
|
|
|
if (ShouldForgetSkippable()) {
|
|
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
|
}
|
|
|
|
if (aDeadline.IsNull()) {
|
|
return {CCRunnerAction::None, Yield};
|
|
}
|
|
|
|
// If we're called during idle time, try to find some work to do by
|
|
// advancing to the next state, effectively bypassing some possible forget
|
|
// skippable calls.
|
|
mCCRunnerState = CCRunnerState::CleanupChildless;
|
|
|
|
// Continue on to CleanupChildless, but only after checking IsCCNeeded
|
|
// again.
|
|
return {CCRunnerAction::None, Continue};
|
|
|
|
// CleanupChildless: do a stronger ForgetSkippable that removes nodes with
|
|
// no children in the cycle collector graph. This state is split into 3
|
|
// parts; the other Cleanup* actions will happen within the same callback
|
|
// (unless the ForgetSkippable shrinks the purple buffer enough for the CC
|
|
// to be skipped entirely.)
|
|
case CCRunnerState::CleanupChildless:
|
|
mCCRunnerState = CCRunnerState::CleanupContentUnbinder;
|
|
return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless};
|
|
|
|
// CleanupContentUnbinder: continuing cleanup, clear out the content
|
|
// unbinder.
|
|
case CCRunnerState::CleanupContentUnbinder:
|
|
if (aDeadline.IsNull()) {
|
|
// Non-idle (waiting) callbacks skip the rest of the cleanup, but still
|
|
// wait for another fire before the actual CC.
|
|
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
|
return {CCRunnerAction::None, Yield};
|
|
}
|
|
|
|
// Running in an idle callback.
|
|
|
|
// The deadline passed, so go straight to CC in the next slice.
|
|
if (now >= aDeadline) {
|
|
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
|
return {CCRunnerAction::None, Yield};
|
|
}
|
|
|
|
mCCRunnerState = CCRunnerState::CleanupDeferred;
|
|
return {CCRunnerAction::CleanupContentUnbinder, Continue};
|
|
|
|
// CleanupDeferred: continuing cleanup, do deferred deletion.
|
|
case CCRunnerState::CleanupDeferred:
|
|
MOZ_ASSERT(!aDeadline.IsNull(),
|
|
"Should only be in CleanupDeferred state when idle");
|
|
|
|
// Our efforts to avoid a CC have failed. Let the timer fire once more
|
|
// to trigger a CC.
|
|
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
|
if (now >= aDeadline) {
|
|
// The deadline passed, go straight to CC in the next slice.
|
|
return {CCRunnerAction::None, Yield};
|
|
}
|
|
|
|
return {CCRunnerAction::CleanupDeferred, Yield};
|
|
|
|
// StartCycleCollection: start actually doing cycle collection slices.
|
|
case CCRunnerState::StartCycleCollection:
|
|
// We are in the final timer fire and still meet the conditions for
|
|
// triggering a CC. Let RunCycleCollectorSlice finish the current IGC if
|
|
// any, because that will allow us to include the GC time in the CC pause.
|
|
mCCRunnerState = CCRunnerState::CycleCollecting;
|
|
[[fallthrough]];
|
|
|
|
// CycleCollecting: continue running slices until done.
|
|
case CCRunnerState::CycleCollecting:
|
|
return {CCRunnerAction::CycleCollect, Yield};
|
|
|
|
default:
|
|
MOZ_CRASH("Unexpected CCRunner state");
|
|
};
|
|
}
|
|
|
|
inline js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget(
|
|
TimeStamp aStartTimeStamp, TimeStamp aDeadline) {
|
|
if (mForgetSkippableFrequencyStartTime.IsNull()) {
|
|
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
|
} else if (aStartTimeStamp - mForgetSkippableFrequencyStartTime >
|
|
kOneMinute) {
|
|
TimeStamp startPlusMinute = mForgetSkippableFrequencyStartTime + kOneMinute;
|
|
|
|
// If we had forget skippables only at the beginning of the interval, we
|
|
// still want to use the whole time, minute or more, for frequency
|
|
// calculation. mLastForgetSkippableEndTime is needed if forget skippable
|
|
// takes enough time to push the interval to be over a minute.
|
|
TimeStamp endPoint = std::max(startPlusMinute, mLastForgetSkippableEndTime);
|
|
|
|
// Duration in minutes.
|
|
double duration =
|
|
(endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
|
|
uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration);
|
|
Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
|
|
frequencyPerMinute);
|
|
mForgetSkippableCounter = 0;
|
|
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
|
}
|
|
++mForgetSkippableCounter;
|
|
|
|
TimeDuration budgetTime =
|
|
aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration;
|
|
return js::SliceBudget(budgetTime);
|
|
}
|
|
|
|
} // namespace mozilla
|