Bug 1599445 - Refactor CCRunnerFired function by writing it as a state machine r=mccr8

This makes the function into a state machine implemented with a switch statement and gets rid of the recursive call. This results in a little duplication of logic but hopefully it is easier to understand overall. What do you think?

Differential Revision: https://phabricator.services.mozilla.com/D54750

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jon Coppeard 2019-11-27 10:18:17 +00:00
Родитель 883d58bccf
Коммит 59a54ab596
1 изменённых файлов: 108 добавлений и 57 удалений

Просмотреть файл

@ -148,6 +148,8 @@ static const uint32_t kCCPurpleLimit = 200;
// if you add statics here, add them to the list in StartupJSEnvironment
enum class CCRunnerState { Inactive, EarlyTimer, LateTimer, FinalTimer };
static nsITimer* sGCTimer;
static nsITimer* sShrinkingGCTimer;
static StaticRefPtr<IdleTaskRunner> sCCRunner;
@ -161,6 +163,8 @@ static TimeStamp sLastForgetSkippableCycleEndTime;
static TimeStamp sCurrentGCStartTime;
static CCRunnerState sCCRunnerState = CCRunnerState::Inactive;
static TimeDuration sCCDelay = kCCDelay;
static bool sCCLockedOut;
static TimeStamp sCCLockedOutTime;
@ -171,7 +175,7 @@ static bool sHasRunGC;
static uint32_t sCCollectedWaitingForGC;
static uint32_t sCCollectedZonesWaitingForGC;
static uint32_t sLikelyShortLivingObjectsNeedingGC;
static int32_t sCCRunnerFireCount = 0;
static int32_t sCCRunnerEarlyFireCount = 0;
static uint32_t sPreviousSuspectedCount = 0;
static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
static bool sNeedsFullCC = false;
@ -1812,92 +1816,138 @@ static bool ShouldTriggerCC(uint32_t aSuspected) {
TimeUntilNow(sLastCCEndTime) > 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;
}
static TimeDuration ccDelay = kCCDelay;
if (sCCLockedOut) {
ccDelay = kCCDelay / int64_t(3);
TimeStamp now = TimeStamp::Now();
if (!sCCLockedOutTime) {
// Reset sCCRunnerFireCount so that we run forgetSkippable
// often enough before CC. Because of reduced ccDelay
// forgetSkippable will be called just a few times.
// kMaxCCLockedoutTime limit guarantees that we end up calling
// 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.
sCCRunnerFireCount = 0;
sCCRunnerState = CCRunnerState::EarlyTimer;
sCCRunnerEarlyFireCount = 0;
sCCDelay = kCCDelay / int64_t(3);
sCCLockedOutTime = now;
return false;
}
if (now - sCCLockedOutTime < kMaxCCLockedoutTime) {
return false;
}
}
++sCCRunnerFireCount;
bool didDoWork = false;
bool finished = false;
// During early timer fires, we only run forgetSkippable. During the first
// late timer fire, we decide if we are going to have a second and final
// late timer fire, where we may begin to run the CC. Should run at least one
// early timer fire to allow cleanup before the CC.
int32_t numEarlyTimerFires =
std::max(int32_t(ccDelay / kCCSkippableDelay) - 2, 1);
bool isLateTimerFire = sCCRunnerFireCount > numEarlyTimerFires;
uint32_t suspected = nsCycleCollector_suspectedCount();
if (isLateTimerFire && ShouldTriggerCC(suspected)) {
if (sCCRunnerFireCount == numEarlyTimerFires + 1) {
FireForgetSkippable(suspected, true, aDeadline);
didDoWork = true;
if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
// Our efforts to avoid a CC have failed, so we return to let the
// timer fire once more to trigger a CC.
if (!aDeadline.IsNull() && TimeStamp::Now() < aDeadline) {
// Clear content unbinder before the first CC slice.
Element::ClearContentUnbinder();
if (TimeStamp::Now() < aDeadline) {
// And trigger deferred deletion too.
nsCycleCollector_doDeferredDeletion();
}
}
return didDoWork;
switch (sCCRunnerState) {
case CCRunnerState::EarlyTimer:
++sCCRunnerEarlyFireCount;
if (IsLastEarlyCCTimer(sCCRunnerEarlyFireCount)) {
sCCRunnerState = CCRunnerState::LateTimer;
}
} else {
if (ShouldFireForgetSkippable(suspected)) {
FireForgetSkippable(suspected, /* aRemoveChildless = */ false,
aDeadline);
didDoWork = true;
break;
}
if (aDeadline.IsNull()) {
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;
MOZ_FALLTHROUGH;
case CCRunnerState::LateTimer:
if (!ShouldTriggerCC(suspected) && 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.
Element::ClearContentUnbinder();
if (TimeStamp::Now() < aDeadline) {
// And trigger deferred deletion too.
nsCycleCollector_doDeferredDeletion();
}
}
break;
case CCRunnerState::FinalTimer:
if (!ShouldTriggerCC(suspected) && 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;
}
} else if (((sPreviousSuspectedCount + 100) <= suspected) ||
(sCleanupsSinceLastGC < kMajorForgetSkippableCalls)) {
// Only do a forget skippable if there are more than a few new objects
// or we're doing the initial forget skippables.
FireForgetSkippable(suspected, false, aDeadline);
didDoWork = true;
} else if (!isLateTimerFire && !aDeadline.IsNull()) {
MOZ_ASSERT(!didDoWork);
// If we're called during idle time, try to find some work to do by calling
// the method recursively, effectively bypassing some possible forget
// skippable calls.
sCCRunnerFireCount = numEarlyTimerFires;
return CCRunnerFired(aDeadline);
finished = true;
break;
default:
MOZ_CRASH("Unexpected CCRunner state");
}
if (isLateTimerFire) {
ccDelay = kCCDelay;
// We have either just run the CC or decided we don't want to run the CC
// next time, so kill the timer.
if (finished) {
sPreviousSuspectedCount = 0;
nsJSContext::KillCCRunner();
if (!didDoWork) {
sLastForgetSkippableCycleEndTime = TimeStamp::Now();
}
@ -2092,11 +2142,11 @@ void nsJSContext::MaybePokeCC() {
}
if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
sCCRunnerFireCount = 0;
// We can kill some objects before running forgetSkippable.
nsCycleCollector_dispatchDeferredDeletion();
ActivateCCRunner();
sCCRunner = IdleTaskRunner::Create(
CCRunnerFired, "MaybePokeCC::CCRunnerFired",
kCCSkippableDelay.ToMilliseconds(),
@ -2138,6 +2188,7 @@ void nsJSContext::KillShrinkingGCTimer() {
// static
void nsJSContext::KillCCRunner() {
sCCLockedOutTime = TimeStamp();
sCCRunnerState = CCRunnerState::Inactive;
if (sCCRunner) {
sCCRunner->Cancel();
sCCRunner = nullptr;