Backed out 10 changesets (bug 1662254) for lint failure on generate_static_pref_list.py CLOSED TREE

Backed out changeset 972e63387191 (bug 1662254)
Backed out changeset 3a9d6f0aa380 (bug 1662254)
Backed out changeset 4b3ffe7fe1af (bug 1662254)
Backed out changeset 4834b1025611 (bug 1662254)
Backed out changeset ec1f7afe8a2d (bug 1662254)
Backed out changeset c7a6b927b1de (bug 1662254)
Backed out changeset 2427d85b2605 (bug 1662254)
Backed out changeset 979bf4ac97bb (bug 1662254)
Backed out changeset 3e443333c636 (bug 1662254)
Backed out changeset a5261578ebfc (bug 1662254)
This commit is contained in:
Bogdan Tara 2020-11-12 23:59:07 +02:00
parent a32c788631
commit 3667467db8
12 changed files with 479 additions and 536 deletions

View File

@ -118,7 +118,7 @@ marker.gcreason.label.PERIODIC_FULL_GC=Periodic Full GC
marker.gcreason.label.INCREMENTAL_TOO_SLOW=Allocations Rate Too Fast
marker.gcreason.label.COMPONENT_UTILS=Cu.forceGC
marker.gcreason.label.MEM_PRESSURE=Low Memory
marker.gcreason.label.CC_FINISHED=Cycle Collection Finished
marker.gcreason.label.CC_WAITING=Forced by Cycle Collection
marker.gcreason.label.CC_FORCED=Forced by Cycle Collection
marker.gcreason.label.LOAD_END=Page Load Finished
marker.gcreason.label.PAGE_HIDE=Moved to Background

View File

@ -3,15 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/SliceBudget.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/MainThreadIdlePeriod.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/MainThreadIdlePeriod.h"
#include "nsCycleCollector.h"
namespace mozilla {
static const mozilla::TimeDuration kOneMinute =
mozilla::TimeDuration::FromSeconds(60.0f);
@ -62,35 +57,19 @@ static const mozilla::TimeDuration kMaxCCLockedoutTime =
// 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
};
// if you add statics here, add them to the list in StartupJSEnvironment
enum CCRunnerYield { Continue, Yield };
namespace mozilla {
enum CCRunnerForgetSkippableRemoveChildless {
KeepChildless = false,
RemoveChildless = true
};
MOZ_ALWAYS_INLINE
static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) {
MOZ_ASSERT(aEnd >= aStart);
return aEnd - aStart;
}
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;
};
static inline js::SliceBudget BudgetFromDuration(TimeDuration aDuration) {
return js::SliceBudget(js::TimeBudget(aDuration.ToMilliseconds()));
}
class CCGCScheduler {
public:
@ -102,10 +81,12 @@ class CCGCScheduler {
// State retrieval
TimeDuration GetCCBlockedTime(TimeStamp aNow) const {
MOZ_ASSERT(mInIncrementalGC);
MOZ_ASSERT(!mCCBlockStart.IsNull());
return aNow - mCCBlockStart;
Maybe<TimeDuration> GetCCBlockedTime(TimeStamp now) const {
MOZ_ASSERT_IF(mCCBlockStart.IsNull(), !mInIncrementalGC);
if (mCCBlockStart.IsNull()) {
return {};
}
return Some(now - mCCBlockStart);
}
bool InIncrementalGC() const { return mInIncrementalGC; }
@ -116,19 +97,9 @@ class CCGCScheduler {
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 SetNeedsFullCC() { mNeedsFullCC = true; }
void NoteGCBegin() {
// Treat all GC as incremental here; non-incremental GC will just appear to
@ -137,24 +108,24 @@ class CCGCScheduler {
}
void NoteGCEnd() {
mInIncrementalGC = false;
mCCBlockStart = TimeStamp();
mNeedsFullCC = true;
mHasRunGC = true;
mCleanupsSinceLastGC = 0;
mCCollectedWaitingForGC = 0;
mCCollectedZonesWaitingForGC = 0;
mLikelyShortLivingObjectsNeedingGC = 0;
mInIncrementalGC = false;
}
// 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) {
enum IsStartingCCLockout { StartingLockout = true, AlreadyLockedOut = false };
IsStartingCCLockout EnsureCCIsBlocked(TimeStamp aNow) {
MOZ_ASSERT(mInIncrementalGC);
MOZ_ASSERT(mCCBlockStart.IsNull());
if (mCCBlockStart) {
return AlreadyLockedOut;
}
mCCBlockStart = aNow;
return StartingLockout;
}
void UnblockCC() { mCCBlockStart = TimeStamp(); }
@ -169,23 +140,9 @@ class CCGCScheduler {
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
@ -194,22 +151,75 @@ class CCGCScheduler {
mLastForgetSkippableCycleEndTime = TimeStamp::Now();
}
void Shutdown() { mDidShutdown = true; }
// Scheduling
TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline) 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 - TimeStamp::Now();
if (!mCCBlockStart) {
return budget;
}
TimeDuration blockedTime = TimeStamp::Now() - mCCBlockStart;
TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
double percentOfBlockedTime =
std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
return std::max(budget, maxSliceGCBudget.MultDouble(percentOfBlockedTime));
}
// 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;
js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
TimeStamp aCCBeginTime,
TimeStamp aPrevSliceEndTime,
bool* aPreferShorterSlices) const {
TimeStamp now = TimeStamp::Now();
inline TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline,
TimeStamp aNow) const;
*aPreferShorterSlices =
aDeadline.IsNull() || (aDeadline - now) < kICCSliceBudget;
bool ShouldForgetSkippable(uint32_t aSuspected) const {
TimeDuration baseBudget =
aDeadline.IsNull() ? kICCSliceBudget : aDeadline - now;
if (aCCBeginTime.IsNull()) {
// If no CC is in progress, use the standard slice time.
return BudgetFromDuration(baseBudget);
}
// Only run a limited slice if we're within the max running time.
TimeDuration runningTime = TimeBetween(aCCBeginTime, now);
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.
double sliceDelayMultiplier =
TimeBetween(aPrevSliceEndTime, now) / 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 BudgetFromDuration(
std::max({delaySliceBudget, laterSliceBudget, baseBudget}));
}
bool ShouldFireForgetSkippable(uint32_t aSuspected) 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) <= aSuspected) ||
@ -227,64 +237,262 @@ class CCGCScheduler {
aNow - mLastCCEndTime > kCCForced);
}
inline bool ShouldScheduleCC() const;
bool ShouldScheduleCC() const {
TimeStamp now = TimeStamp::Now();
// 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;
// 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(nsCycleCollector_suspectedCount(), now);
}
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const {
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
int32_t numEarlyTimerFires =
std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
return aCurrentFireCount >= numEarlyTimerFires;
}
enum class CCRunnerAction {
None,
ForgetSkippable,
CleanupContentUnbinder,
CleanupDeferred,
CycleCollect,
StopRunning
};
enum class CCRunnerState {
Inactive,
ReducePurple,
CleanupChildless,
CleanupContentUnbinder,
CleanupDeferred,
StartCycleCollection,
CycleCollecting,
Canceled,
NumStates
CycleCollect
};
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;
enum CCRunnerYield { Continue, Yield };
// 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");
}
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;
};
void ActivateCCRunner() {
MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive);
mCCRunnerState = CCRunnerState::ReducePurple;
mCCDelay = kCCDelay;
mCCRunnerEarlyFireCount = 0;
}
void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; }
inline CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline,
uint32_t aSuspected);
CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline, uint32_t aSuspected) {
if (mCCRunnerState == CCRunnerState::Inactive) {
// When we cancel a cycle, there may have been a final ForgetSkippable.
return {CCRunnerAction::StopRunning, Yield};
}
TimeStamp now = TimeStamp::Now();
if (InIncrementalGC()) {
if (EnsureCCIsBlocked(now) == StartingLockout) {
// Reset our state so that we run forgetSkippable often enough before
// CC. Because of reduced mCCDelay forgetSkippable will be called just
// a few times. kMaxCCLockedoutTime limit guarantees that we end up
// calling forgetSkippable and CycleCollectNow eventually.
mCCRunnerState = CCRunnerState::ReducePurple;
mCCRunnerEarlyFireCount = 0;
mCCDelay = kCCDelay / int64_t(3);
return {CCRunnerAction::None, Yield};
}
if (GetCCBlockedTime(now).value() < 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).
switch (mCCRunnerState) {
case CCRunnerState::ReducePurple:
case CCRunnerState::CleanupDeferred:
break;
default:
// If we don't pass the threshold for wanting to cycle collect, stop
// now (after possibly doing a final ForgetSkippable).
if (!IsCCNeeded(aSuspected, now)) {
mCCRunnerState = CCRunnerState::Inactive;
NoteForgetSkippableOnlyCycle();
// Preserve the previous code's idea of when to check whether a
// ForgetSkippable should be fired.
if (mCCRunnerState != CCRunnerState::CleanupContentUnbinder &&
ShouldFireForgetSkippable(aSuspected)) {
// The Inactive 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 (ShouldFireForgetSkippable(aSuspected)) {
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 GetNextCCRunnerAction(aDeadline, aSuspected);
// 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::CycleCollect;
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::CycleCollect;
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::CycleCollect;
if (now >= aDeadline) {
// The deadline passed, go straight to CC in the next slice.
return {CCRunnerAction::None, Yield};
}
return {CCRunnerAction::CleanupDeferred, Yield};
// CycleCollect: the final state where we actually do a slice of cycle
// collection and reset the timer.
case CCRunnerState::CycleCollect:
// 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::Inactive;
return {CCRunnerAction::CycleCollect, Yield};
default:
MOZ_CRASH("Unexpected CCRunner state");
};
}
// 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);
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 BudgetFromDuration(budgetTime);
}
private:
// State
// An incremental GC is in progress, which blocks the CC from running for its
@ -294,9 +502,6 @@ class CCGCScheduler {
// 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;
@ -306,330 +511,14 @@ class CCGCScheduler {
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 = TimeStamp::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 = TimeStamp::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(nsCycleCollector_suspectedCount(), now);
}
CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline,
uint32_t aSuspected) {
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(mozilla::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 = TimeStamp::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(aSuspected, 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(aSuspected)) {
// 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(aSuspected)) {
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");
};
}
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

View File

@ -92,14 +92,27 @@ using namespace mozilla::dom;
static nsITimer* sGCTimer;
static nsITimer* sShrinkingGCTimer;
static StaticRefPtr<IdleTaskRunner> sCCRunner;
static StaticRefPtr<IdleTaskRunner> sICCRunner;
static nsITimer* sFullGCTimer;
static StaticRefPtr<IdleTaskRunner> sInterSliceGCRunner;
static TimeStamp sCurrentGCStartTime;
static JS::GCSliceCallback sPrevGCSliceCallback;
static bool sHasRunGC;
static uint32_t sCCollectedWaitingForGC;
static uint32_t sCCollectedZonesWaitingForGC;
static uint32_t sLikelyShortLivingObjectsNeedingGC;
static bool sNeedsFullGC = false;
static bool sNeedsGCAfterCC = false;
static bool sIncrementalCC = false;
static TimeStamp sFirstCollectionTime;
static bool sIsInitialized;
static bool sDidShutdown;
static bool sShuttingDown;
// nsJSEnvironmentObserver observes the user-interaction-inactive notifications
@ -266,7 +279,6 @@ void FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
} /* namespace xpc */
static TimeDuration GetCollectionTimeDelta() {
static TimeStamp sFirstCollectionTime;
TimeStamp now = TimeStamp::Now();
if (sFirstCollectionTime) {
return now - sFirstCollectionTime;
@ -279,10 +291,18 @@ static void KillTimers() {
nsJSContext::KillGCTimer();
nsJSContext::KillShrinkingGCTimer();
nsJSContext::KillCCRunner();
nsJSContext::KillICCRunner();
nsJSContext::KillFullGCTimer();
nsJSContext::KillInterSliceGCRunner();
}
// If we collected a substantial amount of cycles, poke the GC since more
// objects might be unreachable now.
static bool NeedsGCAfterCC() {
return sCCollectedWaitingForGC > 250 || sCCollectedZonesWaitingForGC > 0 ||
sLikelyShortLivingObjectsNeedingGC > 2500 || sNeedsGCAfterCC;
}
class nsJSEnvironmentObserver final : public nsIObserver {
~nsJSEnvironmentObserver() = default;
@ -315,7 +335,7 @@ nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
nsJSContext::NonIncrementalGC,
nsJSContext::ShrinkingGC);
nsJSContext::CycleCollectNow();
if (sScheduler.NeedsGCAfterCC()) {
if (NeedsGCAfterCC()) {
nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE,
nsJSContext::NonIncrementalGC,
nsJSContext::ShrinkingGC);
@ -1091,10 +1111,10 @@ void nsJSContext::GarbageCollectNow(JS::GCReason aReason,
if (aIncremental == NonIncrementalGC ||
aReason == JS::GCReason::FULL_GC_TIMER) {
sScheduler.SetNeedsFullGC();
sNeedsFullGC = true;
}
if (sScheduler.NeedsFullGC()) {
if (sNeedsFullGC) {
JS::PrepareForFullGC(cx);
}
@ -1158,12 +1178,6 @@ static void FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless,
}
}
MOZ_ALWAYS_INLINE
static TimeDuration TimeBetween(TimeStamp aStart, TimeStamp aEnd) {
MOZ_ASSERT(aEnd >= aStart);
return aEnd - aStart;
}
static TimeDuration TimeUntilNow(TimeStamp start) {
if (start.IsNull()) {
return TimeDuration();
@ -1314,11 +1328,10 @@ void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults,
ProcessNameForCollectorLog(), getpid(), mMaxSliceTime.ToMilliseconds(),
mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected,
aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
aResults.mFreedRefCounted, aResults.mFreedGCed,
sScheduler.mCCollectedWaitingForGC,
sScheduler.mCCollectedZonesWaitingForGC,
sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(),
mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC,
sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
gcMsg.get(), mForgetSkippableBeforeCC,
mMinForgetSkippableTime.ToMilliseconds(),
mMaxForgetSkippableTime.ToMilliseconds(),
mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
mTotalForgetSkippableTime.ToMilliseconds(),
@ -1375,11 +1388,10 @@ void CycleCollectorStats::MaybeNotifyStats(
mMaxSliceTime.ToMilliseconds(), mTotalSliceTime.ToMilliseconds(),
mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(),
mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed,
aResults.mFreedRefCounted, aResults.mFreedGCed,
sScheduler.mCCollectedWaitingForGC,
sScheduler.mCCollectedZonesWaitingForGC,
sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC,
mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(),
aResults.mFreedRefCounted, aResults.mFreedGCed, sCCollectedWaitingForGC,
sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
aResults.mForcedGC, mForgetSkippableBeforeCC,
mMinForgetSkippableTime.ToMilliseconds(),
mMaxForgetSkippableTime.ToMilliseconds(),
mTotalForgetSkippableTime.ToMilliseconds() / aCleanups,
mTotalForgetSkippableTime.ToMilliseconds(), mRemovedPurples);
@ -1470,6 +1482,26 @@ uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
}
static bool ICCRunnerFired(TimeStamp aDeadline) {
if (sDidShutdown) {
return false;
}
// Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
// to synchronously finish the GC, which is bad.
if (sScheduler.InIncrementalGC()) {
TimeStamp now = TimeStamp::Now();
sScheduler.EnsureCCIsBlocked(now);
if (sScheduler.GetCCBlockedTime(now).value() < kMaxCCLockedoutTime) {
return false;
}
}
nsJSContext::RunCycleCollectorSlice(aDeadline);
return true;
}
// static
void nsJSContext::BeginCycleCollectionCallback() {
MOZ_ASSERT(NS_IsMainThread());
@ -1479,6 +1511,8 @@ void nsJSContext::BeginCycleCollectionCallback() {
sCCStats.mBeginSliceTime.IsNull() ? startTime : sCCStats.mBeginSliceTime;
sCCStats.mSuspected = nsCycleCollector_suspectedCount();
KillCCRunner();
// Run forgetSkippable synchronously to reduce the size of the CC graph. This
// is particularly useful if we recently finished a GC.
if (sScheduler.IsEarlyForgetSkippable()) {
@ -1489,38 +1523,45 @@ void nsJSContext::BeginCycleCollectionCallback() {
sCCStats.AfterSyncForgetSkippable(startTime);
}
MOZ_ASSERT(!sICCRunner,
"Tried to create a new ICC timer when one already existed.");
if (sShuttingDown) {
return;
}
if (!sCCRunner) {
sScheduler.InitCCRunnerStateMachine(
mozilla::CCGCScheduler::CCRunnerState::CycleCollecting);
}
EnsureCCRunner(kIdleICCSliceBudget, kICCIntersliceDelay);
// Create an ICC timer even if ICC is globally disabled, because we could be
// manually triggering an incremental collection, and we want to be sure to
// finish it.
sICCRunner = IdleTaskRunner::Create(
ICCRunnerFired, "BeginCycleCollectionCallback::ICCRunnerFired",
kICCIntersliceDelay.ToMilliseconds(),
kIdleICCSliceBudget.ToMilliseconds(), true, [] { return sShuttingDown; });
}
// static
void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
MOZ_ASSERT(NS_IsMainThread());
nsJSContext::KillCCRunner();
nsJSContext::KillICCRunner();
// Update timing information for the current slice before we log it, if
// we previously called PrepareForCycleCollectionSlice(). During shutdown
// CCs, this won't happen.
sCCStats.AfterCycleCollectionSlice();
sScheduler.NoteCycleCollected(aResults);
sCCollectedWaitingForGC += aResults.mFreedGCed;
sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
TimeStamp endCCTimeStamp = TimeStamp::Now();
TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp);
if (sScheduler.NeedsGCAfterCC()) {
if (NeedsGCAfterCC()) {
MOZ_ASSERT(StaticPrefs::javascript_options_gc_delay() >
kMaxICCDuration.ToMilliseconds(),
"A max duration ICC shouldn't reduce GC delay to 0");
PokeGC(JS::GCReason::CC_FINISHED, nullptr,
PokeGC(JS::GCReason::CC_WAITING, nullptr,
StaticPrefs::javascript_options_gc_delay() -
std::min(ccNowDuration, kMaxICCDuration).ToMilliseconds());
}
@ -1537,15 +1578,15 @@ void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
// Update global state to indicate we have just run a cycle collection.
sScheduler.NoteCCEnd(endCCTimeStamp);
sNeedsGCAfterCC = false;
sCCStats.Clear();
}
// static
bool InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData) {
MOZ_ASSERT(sScheduler.mActiveIntersliceGCBudget);
TimeDuration budget = sScheduler.ComputeInterSliceGCBudget(aDeadline);
TimeStamp startTimeStamp = TimeStamp::Now();
TimeDuration budget =
sScheduler.ComputeInterSliceGCBudget(aDeadline, startTimeStamp);
TimeDuration duration = sGCUnnotifiedTotalTime;
uintptr_t reason = reinterpret_cast<uintptr_t>(aData);
nsJSContext::GarbageCollectNow(
@ -1615,15 +1656,23 @@ void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) {
}
static bool CCRunnerFired(TimeStamp aDeadline) {
if (sDidShutdown) {
return false;
}
bool didDoWork = false;
using CCRunnerAction = CCGCScheduler::CCRunnerAction;
using CCRunnerStep = CCGCScheduler::CCRunnerStep;
// The CC/GC scheduler (sScheduler) decides what action(s) to take during
// this invocation of the CC runner.
//
// This may be zero, one, or multiple actions. (Zero is when CC is blocked by
// incremental GC, or when the scheduler determined that a CC is no longer
// needed.) Loop until the scheduler finishes this invocation by returning
// `Yield` in step.mYield.
// needed.) Loop until an action is requested that finishes this invocation,
// or the scheduler decides that this invocation should finish by returning
// `Yield`.
CCRunnerStep step;
do {
uint32_t suspected = nsCycleCollector_suspectedCount();
@ -1662,28 +1711,11 @@ static bool CCRunnerFired(TimeStamp aDeadline) {
if (step.mAction != CCRunnerAction::None) {
didDoWork = true;
}
} while (step.mYield == CCRunnerYield::Continue);
} while (step.mYield == CCGCScheduler::CCRunnerYield::Continue);
return didDoWork;
}
// static
void nsJSContext::EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget) {
MOZ_ASSERT(!sShuttingDown);
if (!sCCRunner) {
sCCRunner = IdleTaskRunner::Create(
CCRunnerFired, "EnsureCCRunner::CCRunnerFired", aDelay.ToMilliseconds(),
aBudget.ToMilliseconds(), true, [] { return sShuttingDown; });
} else {
sCCRunner->SetBudget(aBudget.ToMilliseconds());
nsIEventTarget* target = mozilla::GetCurrentEventTarget();
if (target) {
sCCRunner->SetTimer(aDelay.ToMilliseconds(), target);
}
}
}
// static
bool nsJSContext::HasHadCleanupSinceLastGC() {
return sScheduler.IsEarlyForgetSkippable(1);
@ -1704,7 +1736,7 @@ void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
// Force full GCs when called from reftests so that we collect dead zones
// that have not been scheduled for collection.
sScheduler.SetNeedsFullGC();
sNeedsFullGC = true;
}
GCTimerFired(nullptr, reinterpret_cast<void*>(aReason));
return;
@ -1722,8 +1754,14 @@ void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason,
"Don't check the CC timers if the CC is locked out during an iGC.");
if (sCCRunner) {
MOZ_ASSERT(!sICCRunner,
"Shouldn't have both sCCRunner and sICCRunner active at the "
"same time");
sCCRunner->SetDeadline(aDeadline);
runnable = sCCRunner;
} else if (sICCRunner) {
sICCRunner->SetDeadline(aDeadline);
runnable = sICCRunner;
}
}
@ -1800,8 +1838,8 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
if (aObj) {
JS::Zone* zone = JS::GetObjectZone(aObj);
CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
} else if (aReason != JS::GCReason::CC_FINISHED) {
sScheduler.SetNeedsFullGC();
} else if (aReason != JS::GCReason::CC_WAITING) {
sNeedsFullGC = true;
}
if (sGCTimer || sInterSliceGCRunner) {
@ -1810,9 +1848,17 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
}
if (sCCRunner) {
// Make sure CC is called regardless of the size of the purple buffer, and
// GC after it.
sScheduler.EnsureCCThenGC();
// Make sure CC is called...
sScheduler.SetNeedsFullCC();
// and GC after it.
sNeedsGCAfterCC = true;
return;
}
if (sICCRunner) {
// Make sure GC is called after the current CC completes.
// No need to SetNeedsFullCC because we are currently running a CC.
sNeedsGCAfterCC = true;
return;
}
@ -1842,7 +1888,7 @@ void nsJSContext::PokeShrinkingGC() {
// static
void nsJSContext::MaybePokeCC() {
if (sCCRunner || sShuttingDown) {
if (sCCRunner || sICCRunner || !sHasRunGC || sShuttingDown) {
return;
}
@ -1850,11 +1896,12 @@ void nsJSContext::MaybePokeCC() {
// We can kill some objects before running forgetSkippable.
nsCycleCollector_dispatchDeferredDeletion();
if (!sCCRunner) {
sScheduler.InitCCRunnerStateMachine(
mozilla::CCGCScheduler::CCRunnerState::ReducePurple);
}
EnsureCCRunner(kCCSkippableDelay, kForgetSkippableSliceDuration);
sScheduler.ActivateCCRunner();
sCCRunner =
IdleTaskRunner::Create(CCRunnerFired, "MaybePokeCC::CCRunnerFired",
kCCSkippableDelay.ToMilliseconds(),
kForgetSkippableSliceDuration.ToMilliseconds(),
true, [] { return sShuttingDown; });
}
}
@ -1898,12 +1945,20 @@ void nsJSContext::KillCCRunner() {
}
}
// static
void nsJSContext::KillICCRunner() {
sScheduler.UnblockCC();
if (sICCRunner) {
sICCRunner->Cancel();
sICCRunner = nullptr;
}
}
static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
const JS::GCDescription& aDesc) {
NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
static TimeStamp sCurrentGCStartTime;
switch (aProgress) {
case JS::GC_CYCLE_BEGIN: {
// Prevent cycle collections and shrinking during incremental GC.
@ -1936,6 +1991,11 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
// May need to kill the inter-slice GC runner
nsJSContext::KillInterSliceGCRunner();
sCCollectedWaitingForGC = 0;
sCCollectedZonesWaitingForGC = 0;
sLikelyShortLivingObjectsNeedingGC = 0;
sScheduler.SetNeedsFullCC();
sHasRunGC = true;
nsJSContext::MaybePokeCC();
if (aDesc.isZone_) {
@ -1947,13 +2007,16 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
}
} else {
nsJSContext::KillFullGCTimer();
sScheduler.SetNeedsFullGC(false);
}
if (sScheduler.IsCCNeeded(nsCycleCollector_suspectedCount())) {
nsCycleCollector_dispatchDeferredDeletion();
}
if (!aDesc.isZone_) {
sNeedsFullGC = false;
}
Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
TimeUntilNow(sCurrentGCStartTime).ToMilliseconds());
break;
@ -2018,13 +2081,20 @@ void nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; }
void nsJSContext::LikelyShortLivingObjectCreated() {
++sScheduler.mLikelyShortLivingObjectsNeedingGC;
++sLikelyShortLivingObjectsNeedingGC;
}
void mozilla::dom::StartupJSEnvironment() {
// initialize all our statics, so that we can restart XPCOM
sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
sHasRunGC = false;
sCCollectedWaitingForGC = 0;
sCCollectedZonesWaitingForGC = 0;
sLikelyShortLivingObjectsNeedingGC = 0;
sNeedsFullGC = true;
sNeedsGCAfterCC = false;
sIsInitialized = false;
sDidShutdown = false;
sShuttingDown = false;
new (&sScheduler) CCGCScheduler(); // Reset the scheduler state.
sCCStats.Init();
@ -2320,7 +2390,7 @@ void mozilla::dom::ShutdownJSEnvironment() {
KillTimers();
sShuttingDown = true;
sScheduler.Shutdown();
sDidShutdown = true;
}
AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)

View File

@ -108,9 +108,8 @@ class nsJSContext : public nsIScriptContext {
static void KillShrinkingGCTimer();
static void MaybePokeCC();
static void EnsureCCRunner(mozilla::TimeDuration aDelay,
mozilla::TimeDuration aBudget);
static void KillCCRunner();
static void KillICCRunner();
static void KillFullGCTimer();
static void KillInterSliceGCRunner();

View File

@ -508,7 +508,7 @@ namespace JS {
D(DOM_WINDOW_UTILS, FIRST_FIREFOX_REASON) \
D(COMPONENT_UTILS, 34) \
D(MEM_PRESSURE, 35) \
D(CC_FINISHED, 36) \
D(CC_WAITING, 36) \
D(CC_FORCED, 37) \
D(LOAD_END, 38) \
D(UNUSED3, 39) \

View File

@ -65,11 +65,6 @@ class JS_PUBLIC_API SliceBudget {
/* Instantiate as SliceBudget(WorkBudget(n)). */
explicit SliceBudget(WorkBudget work);
explicit SliceBudget(mozilla::TimeDuration time)
: SliceBudget(TimeBudget(time.ToMilliseconds()))
{}
void makeUnlimited() {
MOZ_ASSERT(unlimitedDeadline);
deadline = unlimitedDeadline;

View File

@ -176,7 +176,7 @@ triggered. Known values include the following:
* `"DOM_WINDOW_UTILS"`
* `"COMPONENT_UTILS"`
* `"MEM_PRESSURE"`
* `"CC_FINISHED"`
* `"CC_WAITING"`
* `"CC_FORCED"`
* `"LOAD_END"`
* `"PAGE_HIDE"`

View File

@ -108,9 +108,9 @@
* Correctness reasons:
*
* 3) Do a GC now because correctness depends on some GC property. For
* example, CC_FORCED is where the embedding requires the mark bits to be
* set correctly. Also, EVICT_NURSERY where we need to work on the tenured
* heap.
* example, CC_WAITING is where the embedding requires the mark bits
* to be set correct. Also, EVICT_NURSERY where we need to work on the
* tenured heap.
*
* 4) Do a GC because we are shutting down: e.g. SHUTDOWN_CC or DESTROY_*.
*

View File

@ -5104,8 +5104,7 @@
value: 10000
mirror: always
# After doing a zonal GC, wait this much time (in ms) and then do a full GC,
# unless one is already pending.
# Duration of the dom events full gc delay, in ms.
- name: javascript.options.gc_delay.full
type: uint32_t
value: 60000

View File

@ -57,7 +57,7 @@ RUST_TYPES = {
"float": "f32",
}
HEADER_LINE = "// This file was generated by generate_static_pref_list.py from {input_filename}. DO NOT EDIT."
FIRST_LINE = "// This file was generated by generate_static_pref_list.py. DO NOT EDIT."
MIRROR_TEMPLATES = {
"never": """\
@ -222,21 +222,19 @@ def check_pref_list(pref_list):
prev_pref = pref
def generate_code(pref_list, input_filename):
def generate_code(pref_list):
check_pref_list(pref_list)
first_line = HEADER_LINE.format(input_filename=input_filename)
# The required includes for StaticPrefs_<group>.h.
includes = defaultdict(set)
# StaticPrefList_<group>.h contains all the pref definitions for this
# group.
static_pref_list_group_h = defaultdict(lambda: [first_line, ""])
static_pref_list_group_h = defaultdict(lambda: [FIRST_LINE, ""])
# StaticPrefsCGetters.cpp contains C getters for all the mirrored prefs,
# for use by Rust code.
static_prefs_c_getters_cpp = [first_line, ""]
static_prefs_c_getters_cpp = [FIRST_LINE, ""]
# static_prefs.rs contains C getter declarations and a macro.
static_prefs_rs_decls = []
@ -312,7 +310,7 @@ def generate_code(pref_list, input_filename):
# StaticPrefListAll.h contains one `#include "mozilla/StaticPrefList_X.h`
# line per pref group.
static_pref_list_all_h = [first_line, ""]
static_pref_list_all_h = [FIRST_LINE, ""]
static_pref_list_all_h.extend(
'#include "mozilla/StaticPrefList_{}.h"'.format(group)
for group in sorted(static_pref_list_group_h)
@ -321,7 +319,7 @@ def generate_code(pref_list, input_filename):
# StaticPrefsAll.h contains one `#include "mozilla/StaticPrefs_X.h` line per
# pref group.
static_prefs_all_h = [first_line, ""]
static_prefs_all_h = [FIRST_LINE, ""]
static_prefs_all_h.extend(
'#include "mozilla/StaticPrefs_{}.h"'.format(group)
for group in sorted(static_pref_list_group_h)
@ -332,7 +330,7 @@ def generate_code(pref_list, input_filename):
# used directly by application code.
static_prefs_group_h = defaultdict(list)
for group in sorted(static_pref_list_group_h):
static_prefs_group_h[group] = [first_line]
static_prefs_group_h[group] = [FIRST_LINE]
static_prefs_group_h[group].append(
STATIC_PREFS_GROUP_H_TEMPLATE1.format(group=group)
)
@ -346,7 +344,7 @@ def generate_code(pref_list, input_filename):
)
# static_prefs.rs contains the Rust macro getters.
static_prefs_rs = [first_line, "", 'extern "C" {']
static_prefs_rs = [FIRST_LINE, "", 'extern "C" {']
static_prefs_rs.extend(static_prefs_rs_decls)
static_prefs_rs.extend(["}", "", "#[macro_export]", "macro_rules! pref {"])
static_prefs_rs.extend(static_prefs_rs_macro)
@ -384,8 +382,7 @@ def emit_code(fd, pref_list_filename):
try:
pref_list = yaml.safe_load(pp.out.getvalue())
input_file = os.path.relpath(pref_list_filename, os.environ["TOPSRCDIR"])
code = generate_code(pref_list, input_file)
code = generate_code(pref_list)
except (IOError, ValueError) as e:
print("{}: error:\n {}\n".format(pref_list_filename, e))
sys.exit(1)

View File

@ -76,15 +76,10 @@ static void TimedOut(nsITimer* aTimer, void* aClosure) {
void IdleTaskRunner::SetDeadline(mozilla::TimeStamp aDeadline) {
mDeadline = aDeadline;
}
void IdleTaskRunner::SetBudget(int64_t aBudget) {
mBudget = TimeDuration::FromMilliseconds(aBudget);
}
};
void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTarget->IsOnCurrentThread());
// aTarget is always the main thread event target provided from
// NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that
// CollectorRunner always run specifically the main thread.

View File

@ -14,10 +14,11 @@
namespace mozilla {
// A general purpose repeating callback runner (it can be configured to a
// one-time runner, too.) If it is running repeatedly, one has to either
// explicitly Cancel() the runner or have MayStopProcessing() callback return
// true to completely remove the runner.
// A general purpose repeating callback runner (it can be configured
// to a one-time runner, too.) If it is running repeatedly,
// one has to either explicitly Cancel() the runner or have
// MayContinueProcessing() callback return false to completely remove
// the runner.
class IdleTaskRunner final : public IdleRunnable {
public:
// Return true if some meaningful work was done.
@ -39,8 +40,6 @@ class IdleTaskRunner final : public IdleRunnable {
void SetDeadline(mozilla::TimeStamp aDeadline) override;
void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override;
void SetBudget(int64_t aBudget);
nsresult Cancel() override;
void Schedule(bool aAllowIdleDispatch);