зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1662254 - Move incremental CC slice logic to CCGCScheduler r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D89989
This commit is contained in:
Родитель
13a9ad9955
Коммит
0eb5a80f8f
|
@ -88,8 +88,14 @@ class CCGCScheduler {
|
|||
|
||||
TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; }
|
||||
|
||||
bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const {
|
||||
return mCleanupsSinceLastGC < aN;
|
||||
}
|
||||
|
||||
// State modification
|
||||
|
||||
void SetNeedsFullCC() { mNeedsFullCC = true; }
|
||||
|
||||
void NoteGCBegin() {
|
||||
// Treat all GC as incremental here; non-incremental GC will just appear to
|
||||
// be one slice.
|
||||
|
@ -98,6 +104,7 @@ class CCGCScheduler {
|
|||
|
||||
void NoteGCEnd() {
|
||||
mCCBlockStart = TimeStamp();
|
||||
mCleanupsSinceLastGC = 0;
|
||||
mInIncrementalGC = false;
|
||||
}
|
||||
|
||||
|
@ -118,12 +125,23 @@ class CCGCScheduler {
|
|||
|
||||
void UnblockCC() { mCCBlockStart = TimeStamp(); }
|
||||
|
||||
void NoteForgetSkippableComplete(TimeStamp now) {
|
||||
// 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 = nsCycleCollector_suspectedCount();
|
||||
mPreviousSuspectedCount = suspected;
|
||||
mCleanupsSinceLastGC++;
|
||||
return aSuspectedBeforeForgetSkippable - suspected;
|
||||
}
|
||||
|
||||
void NoteCCEnd(TimeStamp when) { mLastCCEndTime = when; }
|
||||
void NoteCCEnd(TimeStamp aWhen) {
|
||||
mLastCCEndTime = aWhen;
|
||||
mNeedsFullCC = false;
|
||||
}
|
||||
|
||||
// The CC was abandoned without running a slice, so we only did forget
|
||||
// skippables. Prevent running another cycle soon.
|
||||
void NoteForgetSkippableOnlyCycle() {
|
||||
mLastForgetSkippableCycleEndTime = TimeStamp::Now();
|
||||
}
|
||||
|
@ -196,12 +214,28 @@ class CCGCScheduler {
|
|||
std::max({delaySliceBudget, laterSliceBudget, baseBudget}));
|
||||
}
|
||||
|
||||
bool ShouldScheduleCC(uint32_t aCleanupsSinceLastGC) const {
|
||||
TimeStamp now;
|
||||
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) ||
|
||||
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(uint32_t aSuspected, TimeStamp aNow = TimeStamp::Now()) const {
|
||||
return mNeedsFullCC || aSuspected > kCCPurpleLimit ||
|
||||
(aSuspected > kCCForcedPurpleLimit && mLastCCEndTime &&
|
||||
aNow - mLastCCEndTime > kCCForced);
|
||||
}
|
||||
|
||||
bool ShouldScheduleCC() const {
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
// Don't run consecutive CCs too often.
|
||||
if (aCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
|
||||
now = TimeStamp::Now();
|
||||
if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
|
||||
if (now - mLastCCEndTime < kCCDelay) {
|
||||
return false;
|
||||
}
|
||||
|
@ -209,18 +243,181 @@ class CCGCScheduler {
|
|||
|
||||
// If GC hasn't run recently and forget skippable only cycle was run,
|
||||
// don't start a new cycle too soon.
|
||||
if ((aCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
|
||||
if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
|
||||
!mLastForgetSkippableCycleEndTime.IsNull()) {
|
||||
if (now.IsNull()) {
|
||||
now = TimeStamp::Now();
|
||||
}
|
||||
if (now - mLastForgetSkippableCycleEndTime <
|
||||
kTimeBetweenForgetSkippableCycles) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return IsCCNeeded(nsCycleCollector_suspectedCount(), now);
|
||||
}
|
||||
|
||||
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
|
||||
int32_t numEarlyTimerFires =
|
||||
std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
|
||||
|
||||
return aCurrentFireCount >= numEarlyTimerFires;
|
||||
}
|
||||
|
||||
enum class CCRunnerAction {
|
||||
None,
|
||||
ForgetSkippable,
|
||||
PrepForCC,
|
||||
CycleCollect,
|
||||
StopRunning
|
||||
};
|
||||
|
||||
enum class CCRunnerState {
|
||||
Inactive,
|
||||
EarlyTimer,
|
||||
LateTimer,
|
||||
LateTimerPostForgetSkippable,
|
||||
FinalTimer
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
void ActivateCCRunner() {
|
||||
MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive);
|
||||
mCCRunnerState = CCRunnerState::EarlyTimer;
|
||||
mCCDelay = kCCDelay;
|
||||
mCCRunnerEarlyFireCount = 0;
|
||||
}
|
||||
|
||||
void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; }
|
||||
|
||||
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::EarlyTimer;
|
||||
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.
|
||||
}
|
||||
|
||||
switch (mCCRunnerState) {
|
||||
// EarlyTimer: 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::EarlyTimer:
|
||||
++mCCRunnerEarlyFireCount;
|
||||
if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) {
|
||||
mCCRunnerState = CCRunnerState::LateTimer;
|
||||
}
|
||||
|
||||
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::LateTimer;
|
||||
[[fallthrough]];
|
||||
|
||||
// LateTimer: do a stronger ForgetSkippable that removes nodes with no
|
||||
// children in the cycle collector graph. This state is split into two
|
||||
// parts; LateTimerPostForgetSkippable will happen within the same
|
||||
// callback (unless the ForgetSkippable shrinks the purple buffer enough
|
||||
// for the CC to be skipped entirely.)
|
||||
case CCRunnerState::LateTimer:
|
||||
if (!IsCCNeeded(aSuspected, now)) {
|
||||
mCCRunnerState = CCRunnerState::Inactive;
|
||||
NoteForgetSkippableOnlyCycle();
|
||||
if (ShouldFireForgetSkippable(aSuspected)) {
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
||||
}
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
mCCRunnerState = CCRunnerState::LateTimerPostForgetSkippable;
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless};
|
||||
|
||||
// LateTimerPostForgetSkippable: continuing LateTimer, possibly do some
|
||||
// final setup before the actual cycle collection slice.
|
||||
case CCRunnerState::LateTimerPostForgetSkippable:
|
||||
if (!IsCCNeeded(aSuspected, now)) {
|
||||
mCCRunnerState = CCRunnerState::Inactive;
|
||||
NoteForgetSkippableOnlyCycle();
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
// Our efforts to avoid a CC have failed. Let the timer fire once more
|
||||
// to trigger a CC.
|
||||
mCCRunnerState = CCRunnerState::FinalTimer;
|
||||
|
||||
if (!aDeadline.IsNull() && TimeStamp::Now() < aDeadline) {
|
||||
return {CCRunnerAction::PrepForCC, Yield};
|
||||
}
|
||||
|
||||
[[fallthrough]];
|
||||
|
||||
// FinalTimer: the final state where we actually do a slice of cycle
|
||||
// collection and reset the timer.
|
||||
case CCRunnerState::FinalTimer:
|
||||
if (!IsCCNeeded(aSuspected, now)) {
|
||||
mCCRunnerState = CCRunnerState::Inactive;
|
||||
NoteForgetSkippableOnlyCycle();
|
||||
if (ShouldFireForgetSkippable(aSuspected)) {
|
||||
return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
|
||||
}
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -273,6 +470,14 @@ class CCGCScheduler {
|
|||
TimeStamp mLastCCEndTime;
|
||||
TimeStamp mLastForgetSkippableCycleEndTime;
|
||||
|
||||
CCRunnerState mCCRunnerState = CCRunnerState::Inactive;
|
||||
int32_t mCCRunnerEarlyFireCount = 0;
|
||||
TimeDuration mCCDelay = kCCDelay;
|
||||
bool mNeedsFullCC = false;
|
||||
uint32_t mPreviousSuspectedCount = 0;
|
||||
|
||||
uint32_t mCleanupsSinceLastGC = UINT32_MAX;
|
||||
|
||||
// Configuration parameters
|
||||
|
||||
TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);
|
||||
|
|
|
@ -315,8 +315,9 @@ nsresult nsCCUncollectableMarker::Observe(nsISupports* aSubject,
|
|||
!strcmp(aTopic, "cycle-collector-forget-skippable"),
|
||||
"wrong topic");
|
||||
|
||||
// JS cleanup can be slow. Do it only if there has been a GC.
|
||||
const bool cleanupJS = nsJSContext::CleanupsSinceLastGC() == 0 &&
|
||||
// JS cleanup can be slow. Do it only if this is the first forget-skippable
|
||||
// after a GC.
|
||||
const bool cleanupJS = nsJSContext::HasHadCleanupSinceLastGC() &&
|
||||
!strcmp(aTopic, "cycle-collector-forget-skippable");
|
||||
|
||||
const bool prepareForCC = !strcmp(aTopic, "cycle-collector-begin");
|
||||
|
|
|
@ -89,8 +89,6 @@ using namespace mozilla::dom;
|
|||
# undef CompareString
|
||||
#endif
|
||||
|
||||
enum class CCRunnerState { Inactive, EarlyTimer, LateTimer, FinalTimer };
|
||||
|
||||
static nsITimer* sGCTimer;
|
||||
static nsITimer* sShrinkingGCTimer;
|
||||
static StaticRefPtr<IdleTaskRunner> sCCRunner;
|
||||
|
@ -100,9 +98,6 @@ static StaticRefPtr<IdleTaskRunner> sInterSliceGCRunner;
|
|||
|
||||
static TimeStamp sCurrentGCStartTime;
|
||||
|
||||
static CCRunnerState sCCRunnerState = CCRunnerState::Inactive;
|
||||
static TimeDuration sCCDelay = kCCDelay;
|
||||
|
||||
static JS::GCSliceCallback sPrevGCSliceCallback;
|
||||
|
||||
static bool sHasRunGC;
|
||||
|
@ -110,10 +105,6 @@ static bool sHasRunGC;
|
|||
static uint32_t sCCollectedWaitingForGC;
|
||||
static uint32_t sCCollectedZonesWaitingForGC;
|
||||
static uint32_t sLikelyShortLivingObjectsNeedingGC;
|
||||
static int32_t sCCRunnerEarlyFireCount = 0;
|
||||
static uint32_t sPreviousSuspectedCount = 0;
|
||||
static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
|
||||
static bool sNeedsFullCC = false;
|
||||
static bool sNeedsFullGC = false;
|
||||
static bool sNeedsGCAfterCC = false;
|
||||
static bool sIncrementalCC = false;
|
||||
|
@ -1157,18 +1148,15 @@ static void FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless,
|
|||
|
||||
js::SliceBudget budget =
|
||||
sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline);
|
||||
bool earlyForgetSkippable = sCleanupsSinceLastGC < kMajorForgetSkippableCalls;
|
||||
bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable();
|
||||
nsCycleCollector_forgetSkippable(budget, aRemoveChildless,
|
||||
earlyForgetSkippable);
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
sScheduler.NoteForgetSkippableComplete(now);
|
||||
|
||||
sPreviousSuspectedCount = nsCycleCollector_suspectedCount();
|
||||
++sCleanupsSinceLastGC;
|
||||
uint32_t removedPurples =
|
||||
sScheduler.NoteForgetSkippableComplete(now, aSuspected);
|
||||
|
||||
TimeDuration duration = now - startTimeStamp;
|
||||
|
||||
uint32_t removedPurples = aSuspected - sPreviousSuspectedCount;
|
||||
sCCStats.AfterForgetSkippable(duration, removedPurples);
|
||||
|
||||
if (duration.ToSeconds()) {
|
||||
|
@ -1527,8 +1515,8 @@ void nsJSContext::BeginCycleCollectionCallback() {
|
|||
|
||||
// Run forgetSkippable synchronously to reduce the size of the CC graph. This
|
||||
// is particularly useful if we recently finished a GC.
|
||||
if (sCleanupsSinceLastGC < kMajorForgetSkippableCalls) {
|
||||
while (sCleanupsSinceLastGC < kMajorForgetSkippableCalls) {
|
||||
if (sScheduler.IsEarlyForgetSkippable()) {
|
||||
while (sScheduler.IsEarlyForgetSkippable()) {
|
||||
FireForgetSkippable(nsCycleCollector_suspectedCount(), false,
|
||||
TimeStamp());
|
||||
}
|
||||
|
@ -1590,7 +1578,6 @@ void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
|
|||
|
||||
// Update global state to indicate we have just run a cycle collection.
|
||||
sScheduler.NoteCCEnd(endCCTimeStamp);
|
||||
sNeedsFullCC = false;
|
||||
sNeedsGCAfterCC = false;
|
||||
sCCStats.Clear();
|
||||
}
|
||||
|
@ -1668,161 +1655,72 @@ void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) {
|
|||
nsJSContext::ShrinkingGC);
|
||||
}
|
||||
|
||||
static bool ShouldTriggerCC(uint32_t aSuspected) {
|
||||
return sNeedsFullCC || aSuspected > kCCPurpleLimit ||
|
||||
(aSuspected > kCCForcedPurpleLimit &&
|
||||
TimeUntilNow(sScheduler.GetLastCCEndTime()) > kCCForced);
|
||||
}
|
||||
|
||||
static inline bool ShouldFireForgetSkippable(uint32_t aSuspected) {
|
||||
// Only do a forget skippable if there are more than a few new objects
|
||||
// or we're doing the initial forget skippables.
|
||||
return ((sPreviousSuspectedCount + 100) <= aSuspected) ||
|
||||
sCleanupsSinceLastGC < kMajorForgetSkippableCalls;
|
||||
}
|
||||
|
||||
static inline bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
|
||||
int32_t numEarlyTimerFires =
|
||||
std::max(int32_t(sCCDelay / kCCSkippableDelay) - 2, 1);
|
||||
|
||||
return aCurrentFireCount >= numEarlyTimerFires;
|
||||
}
|
||||
|
||||
static void ActivateCCRunner() {
|
||||
MOZ_ASSERT(sCCRunnerState == CCRunnerState::Inactive);
|
||||
sCCRunnerState = CCRunnerState::EarlyTimer;
|
||||
sCCDelay = kCCDelay;
|
||||
sCCRunnerEarlyFireCount = 0;
|
||||
}
|
||||
|
||||
static bool CCRunnerFired(TimeStamp aDeadline) {
|
||||
if (sDidShutdown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sScheduler.InIncrementalGC()) {
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
if (sScheduler.EnsureCCIsBlocked(now) == sScheduler.StartingLockout) {
|
||||
// Reset our state so that we run forgetSkippable often enough before
|
||||
// CC. Because of reduced sCCDelay forgetSkippable will be called just a
|
||||
// few times. kMaxCCLockedoutTime limit guarantees that we end up calling
|
||||
// forgetSkippable and CycleCollectNow eventually.
|
||||
sCCRunnerState = CCRunnerState::EarlyTimer;
|
||||
sCCRunnerEarlyFireCount = 0;
|
||||
sCCDelay = kCCDelay / int64_t(3);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sScheduler.GetCCBlockedTime(now).value() < kMaxCCLockedoutTime) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool didDoWork = false;
|
||||
|
||||
// Either we have run a CC slice, or we have decided no CC is needed.
|
||||
bool finished = false;
|
||||
using CCRunnerAction = CCGCScheduler::CCRunnerAction;
|
||||
using CCRunnerStep = CCGCScheduler::CCRunnerStep;
|
||||
|
||||
uint32_t suspected = nsCycleCollector_suspectedCount();
|
||||
|
||||
switch (sCCRunnerState) {
|
||||
case CCRunnerState::EarlyTimer:
|
||||
++sCCRunnerEarlyFireCount;
|
||||
if (IsLastEarlyCCTimer(sCCRunnerEarlyFireCount)) {
|
||||
sCCRunnerState = CCRunnerState::LateTimer;
|
||||
}
|
||||
|
||||
if (ShouldFireForgetSkippable(suspected)) {
|
||||
FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
|
||||
aDeadline);
|
||||
didDoWork = true;
|
||||
// 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 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();
|
||||
step = sScheduler.GetNextCCRunnerAction(aDeadline, suspected);
|
||||
switch (step.mAction) {
|
||||
case CCRunnerAction::None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (aDeadline.IsNull()) {
|
||||
case CCRunnerAction::ForgetSkippable:
|
||||
// 'Forget skippable' only, then end this invocation.
|
||||
FireForgetSkippable(suspected, bool(step.mRemoveChildless), aDeadline);
|
||||
break;
|
||||
}
|
||||
|
||||
// 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.
|
||||
MOZ_ASSERT(!didDoWork);
|
||||
|
||||
sCCRunnerState = CCRunnerState::LateTimer;
|
||||
[[fallthrough]];
|
||||
|
||||
case CCRunnerState::LateTimer:
|
||||
if (!ShouldTriggerCC(suspected)) {
|
||||
if (ShouldFireForgetSkippable(suspected)) {
|
||||
FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
|
||||
aDeadline);
|
||||
didDoWork = true;
|
||||
}
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
FireForgetSkippable(suspected, /* aRemoveChildless = */ true, aDeadline);
|
||||
didDoWork = true;
|
||||
if (!ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Our efforts to avoid a CC have failed, so we return to let the
|
||||
// timer fire once more to trigger a CC.
|
||||
sCCRunnerState = CCRunnerState::FinalTimer;
|
||||
|
||||
if (!aDeadline.IsNull() && TimeStamp::Now() < aDeadline) {
|
||||
// Clear content unbinder before the first CC slice.
|
||||
case CCRunnerAction::PrepForCC:
|
||||
// Clear content unbinder before the first actual CC slice.
|
||||
Element::ClearContentUnbinder();
|
||||
|
||||
if (TimeStamp::Now() < aDeadline) {
|
||||
// And trigger deferred deletion too.
|
||||
// PrepForCC will only be requested when idle. If we still have time
|
||||
// left before the deadline, trigger deferred deletion too.
|
||||
nsCycleCollector_doDeferredDeletion();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CCRunnerState::FinalTimer:
|
||||
if (!ShouldTriggerCC(suspected)) {
|
||||
if (ShouldFireForgetSkippable(suspected)) {
|
||||
FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
|
||||
aDeadline);
|
||||
didDoWork = true;
|
||||
}
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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.
|
||||
nsJSContext::RunCycleCollectorSlice(aDeadline);
|
||||
didDoWork = true;
|
||||
finished = true;
|
||||
break;
|
||||
case CCRunnerAction::CycleCollect:
|
||||
// Cycle collection slice.
|
||||
nsJSContext::RunCycleCollectorSlice(aDeadline);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unexpected CCRunner state");
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
sPreviousSuspectedCount = 0;
|
||||
nsJSContext::KillCCRunner();
|
||||
if (!didDoWork) {
|
||||
// The CC was abandoned without running a slice, so we only did forget
|
||||
// skippables. Prevent running another cycle soon.
|
||||
sScheduler.NoteForgetSkippableOnlyCycle();
|
||||
case CCRunnerAction::StopRunning:
|
||||
// End this CC, either because we have run a cycle collection slice, or
|
||||
// because a CC is no longer needed.
|
||||
nsJSContext::KillCCRunner();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (step.mAction != CCRunnerAction::None) {
|
||||
didDoWork = true;
|
||||
}
|
||||
} while (step.mYield == CCGCScheduler::CCRunnerYield::Continue);
|
||||
|
||||
return didDoWork;
|
||||
}
|
||||
|
||||
// static
|
||||
uint32_t nsJSContext::CleanupsSinceLastGC() { return sCleanupsSinceLastGC; }
|
||||
bool nsJSContext::HasHadCleanupSinceLastGC() {
|
||||
return sScheduler.IsEarlyForgetSkippable(1);
|
||||
}
|
||||
|
||||
// Check all of the various collector timers/runners and see if they are waiting
|
||||
// to fire. This does not check sFullGCTimer, as that's a more expensive
|
||||
|
@ -1952,7 +1850,7 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
|
|||
|
||||
if (sCCRunner) {
|
||||
// Make sure CC is called...
|
||||
sNeedsFullCC = true;
|
||||
sScheduler.SetNeedsFullCC();
|
||||
// and GC after it.
|
||||
sNeedsGCAfterCC = true;
|
||||
return;
|
||||
|
@ -1960,7 +1858,7 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
|
|||
|
||||
if (sICCRunner) {
|
||||
// Make sure GC is called after the current CC completes.
|
||||
// No need to set sNeedsFullCC because we are currently running a CC.
|
||||
// No need to SetNeedsFullCC because we are currently running a CC.
|
||||
sNeedsGCAfterCC = true;
|
||||
return;
|
||||
}
|
||||
|
@ -1995,12 +1893,11 @@ void nsJSContext::MaybePokeCC() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (sScheduler.ShouldScheduleCC(sCleanupsSinceLastGC) &&
|
||||
ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
|
||||
if (sScheduler.ShouldScheduleCC()) {
|
||||
// We can kill some objects before running forgetSkippable.
|
||||
nsCycleCollector_dispatchDeferredDeletion();
|
||||
|
||||
ActivateCCRunner();
|
||||
sScheduler.ActivateCCRunner();
|
||||
sCCRunner =
|
||||
IdleTaskRunner::Create(CCRunnerFired, "MaybePokeCC::CCRunnerFired",
|
||||
kCCSkippableDelay.ToMilliseconds(),
|
||||
|
@ -2042,7 +1939,7 @@ void nsJSContext::KillShrinkingGCTimer() {
|
|||
// static
|
||||
void nsJSContext::KillCCRunner() {
|
||||
sScheduler.UnblockCC();
|
||||
sCCRunnerState = CCRunnerState::Inactive;
|
||||
sScheduler.DeactivateCCRunner();
|
||||
if (sCCRunner) {
|
||||
sCCRunner->Cancel();
|
||||
sCCRunner = nullptr;
|
||||
|
@ -2098,8 +1995,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
|
|||
sCCollectedWaitingForGC = 0;
|
||||
sCCollectedZonesWaitingForGC = 0;
|
||||
sLikelyShortLivingObjectsNeedingGC = 0;
|
||||
sCleanupsSinceLastGC = 0;
|
||||
sNeedsFullCC = true;
|
||||
sScheduler.SetNeedsFullCC();
|
||||
sHasRunGC = true;
|
||||
nsJSContext::MaybePokeCC();
|
||||
|
||||
|
@ -2114,7 +2010,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
|
|||
nsJSContext::KillFullGCTimer();
|
||||
}
|
||||
|
||||
if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
|
||||
if (sScheduler.IsCCNeeded(nsCycleCollector_suspectedCount())) {
|
||||
nsCycleCollector_dispatchDeferredDeletion();
|
||||
}
|
||||
|
||||
|
@ -2150,7 +2046,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
|
|||
[] { return sShuttingDown; });
|
||||
}
|
||||
|
||||
if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
|
||||
if (sScheduler.IsCCNeeded(nsCycleCollector_suspectedCount())) {
|
||||
nsCycleCollector_dispatchDeferredDeletion();
|
||||
}
|
||||
|
||||
|
@ -2196,7 +2092,6 @@ void mozilla::dom::StartupJSEnvironment() {
|
|||
sCCollectedWaitingForGC = 0;
|
||||
sCCollectedZonesWaitingForGC = 0;
|
||||
sLikelyShortLivingObjectsNeedingGC = 0;
|
||||
sNeedsFullCC = false;
|
||||
sNeedsFullGC = true;
|
||||
sNeedsGCAfterCC = false;
|
||||
sIsInitialized = false;
|
||||
|
|
|
@ -116,7 +116,7 @@ class nsJSContext : public nsIScriptContext {
|
|||
// Calling LikelyShortLivingObjectCreated() makes a GC more likely.
|
||||
static void LikelyShortLivingObjectCreated();
|
||||
|
||||
static uint32_t CleanupsSinceLastGC();
|
||||
static bool HasHadCleanupSinceLastGC();
|
||||
|
||||
nsIScriptGlobalObject* GetCachedGlobalObject() {
|
||||
// Verify that we have a global so that this
|
||||
|
|
Загрузка…
Ссылка в новой задаче