Bug 1678416 - Merge ICCRunner and CCRunner into a single CCRunner r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D90633
This commit is contained in:
Steve Fink 2020-12-04 16:03:07 +00:00
Родитель 630f77d442
Коммит bd6330b7a4
3 изменённых файлов: 90 добавлений и 106 удалений

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

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/SliceBudget.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/MainThreadIdlePeriod.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
@ -85,12 +86,10 @@ class CCGCScheduler {
// State retrieval
Maybe<TimeDuration> GetCCBlockedTime(TimeStamp now) const {
MOZ_ASSERT_IF(mCCBlockStart.IsNull(), !mInIncrementalGC);
if (mCCBlockStart.IsNull()) {
return {};
}
return Some(now - mCCBlockStart);
TimeDuration GetCCBlockedTime(TimeStamp aNow) const {
MOZ_ASSERT(mInIncrementalGC);
MOZ_ASSERT(!mCCBlockStart.IsNull());
return aNow - mCCBlockStart;
}
bool InIncrementalGC() const { return mInIncrementalGC; }
@ -122,6 +121,7 @@ class CCGCScheduler {
}
void NoteGCEnd() {
mInIncrementalGC = false;
mCCBlockStart = TimeStamp();
mInIncrementalGC = false;
mNeedsFullCC = true;
@ -136,16 +136,10 @@ class CCGCScheduler {
// 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.
enum IsStartingCCLockout { StartingLockout = true, AlreadyLockedOut = false };
IsStartingCCLockout EnsureCCIsBlocked(TimeStamp aNow) {
void BlockCC(TimeStamp aNow) {
MOZ_ASSERT(mInIncrementalGC);
if (mCCBlockStart) {
return AlreadyLockedOut;
}
MOZ_ASSERT(mCCBlockStart.IsNull());
mCCBlockStart = aNow;
return StartingLockout;
}
void UnblockCC() { mCCBlockStart = TimeStamp(); }
@ -307,7 +301,7 @@ class CCGCScheduler {
mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC;
}
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const {
int32_t numEarlyTimerFires =
std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
@ -329,7 +323,8 @@ class CCGCScheduler {
CleanupChildless,
CleanupContentUnbinder,
CleanupDeferred,
CycleCollect
StartCycleCollection,
CycleCollecting
};
enum CCRunnerYield { Continue, Yield };
@ -353,11 +348,17 @@ class CCGCScheduler {
CCRunnerForgetSkippableRemoveChildless mRemoveChildless;
};
void ActivateCCRunner() {
void InitCCRunnerStateMachine(CCRunnerState initialState) {
MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive);
mCCRunnerState = CCRunnerState::ReducePurple;
mCCDelay = kCCDelay;
mCCRunnerEarlyFireCount = 0;
mCCRunnerState = initialState;
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; }
@ -375,18 +376,29 @@ class CCGCScheduler {
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);
if (mCCBlockStart.IsNull()) {
BlockCC(now);
// 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.
// Otherwise if we already made it to the CycleCollecting state, then
// just ignore CC timer fires while an incremental GC is running.
// (Running ICC during an IGC would cause us to synchronously finish
// the GC, which is bad.)
//
// 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).value() < kMaxCCLockedoutTime) {
if (GetCCBlockedTime(now) < kMaxCCLockedoutTime) {
return {CCRunnerAction::None, Yield};
}
@ -400,6 +412,8 @@ class CCGCScheduler {
switch (mCCRunnerState) {
case CCRunnerState::ReducePurple:
case CCRunnerState::CleanupDeferred:
case CCRunnerState::StartCycleCollection:
case CCRunnerState::CycleCollecting:
break;
default:
@ -464,7 +478,7 @@ class CCGCScheduler {
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;
mCCRunnerState = CCRunnerState::StartCycleCollection;
return {CCRunnerAction::None, Yield};
}
@ -472,7 +486,7 @@ class CCGCScheduler {
// The deadline passed, so go straight to CC in the next slice.
if (now >= aDeadline) {
mCCRunnerState = CCRunnerState::CycleCollect;
mCCRunnerState = CCRunnerState::StartCycleCollection;
return {CCRunnerAction::None, Yield};
}
@ -486,7 +500,7 @@ class CCGCScheduler {
// Our efforts to avoid a CC have failed. Let the timer fire once more
// to trigger a CC.
mCCRunnerState = CCRunnerState::CycleCollect;
mCCRunnerState = CCRunnerState::StartCycleCollection;
if (now >= aDeadline) {
// The deadline passed, go straight to CC in the next slice.
return {CCRunnerAction::None, Yield};
@ -494,14 +508,22 @@ class CCGCScheduler {
return {CCRunnerAction::CleanupDeferred, Yield};
// CycleCollect: the final state where we actually do a slice of cycle
// collection and reset the timer.
case CCRunnerState::CycleCollect:
// StartCycleCollection: start actually doing cycle collection slices.
// The difference between this state and CycleCollecting is that
// StartCycleCollection will return to ReducePurple if an incremental GC
// starts; CycleCollecting will not. (Synchronous CCs go straight to
// CycleCollecting without ever passing through StartCycleCollection or
// any of the other states.)
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::Inactive;
mCCRunnerState = CCRunnerState::CycleCollecting;
[[fallthrough]];
// CycleCollecting: continue running slices until done.
case CCRunnerState::CycleCollecting:
return {CCRunnerAction::CycleCollect, Yield};
default:
@ -544,6 +566,7 @@ class CCGCScheduler {
return BudgetFromDuration(budgetTime);
}
private:
// State
// An incremental GC is in progress, which blocks the CC from running for its

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

@ -94,7 +94,6 @@ 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;
@ -285,7 +284,6 @@ static void KillTimers() {
nsJSContext::KillGCTimer();
nsJSContext::KillShrinkingGCTimer();
nsJSContext::KillCCRunner();
nsJSContext::KillICCRunner();
nsJSContext::KillFullGCTimer();
nsJSContext::KillInterSliceGCRunner();
}
@ -1471,26 +1469,6 @@ uint32_t nsJSContext::GetMaxCCSliceTimeSinceClear() {
return sCCStats.mMaxSliceTimeSinceClear.ToMilliseconds();
}
static bool ICCRunnerFired(TimeStamp aDeadline) {
if (sScheduler.mDidShutdown) {
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());
@ -1500,8 +1478,6 @@ 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()) {
@ -1512,27 +1488,22 @@ void nsJSContext::BeginCycleCollectionCallback() {
sCCStats.AfterSyncForgetSkippable(startTime);
}
MOZ_ASSERT(!sICCRunner,
"Tried to create a new ICC timer when one already existed.");
if (sShuttingDown) {
return;
}
// 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; });
if (!sCCRunner) {
sScheduler.InitCCRunnerStateMachine(
mozilla::CCGCScheduler::CCRunnerState::CycleCollecting);
}
EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget);
}
// static
void nsJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) {
MOZ_ASSERT(NS_IsMainThread());
nsJSContext::KillICCRunner();
nsJSContext::KillCCRunner();
// Update timing information for the current slice before we log it, if
// we previously called PrepareForCycleCollectionSlice(). During shutdown
@ -1642,10 +1613,6 @@ void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) {
}
static bool CCRunnerFired(TimeStamp aDeadline) {
if (sScheduler.mDidShutdown) {
return false;
}
bool didDoWork = false;
using CCRunnerAction = CCGCScheduler::CCRunnerAction;
@ -1702,6 +1669,23 @@ static bool CCRunnerFired(TimeStamp aDeadline) {
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);
@ -1740,14 +1724,8 @@ 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;
}
}
@ -1840,13 +1818,6 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
return;
}
if (sICCRunner) {
// Make sure GC is called after the current CC completes.
// No need to SetNeedsFullCC because we are currently running a CC.
sScheduler.mNeedsGCAfterCC = true;
return;
}
static bool first = true;
NS_NewTimerWithFuncCallback(
@ -1873,7 +1844,7 @@ void nsJSContext::PokeShrinkingGC() {
// static
void nsJSContext::MaybePokeCC() {
if (sCCRunner || sICCRunner || !sScheduler.mHasRunGC || sShuttingDown) {
if (sCCRunner || sShuttingDown) {
return;
}
@ -1881,12 +1852,11 @@ void nsJSContext::MaybePokeCC() {
// We can kill some objects before running forgetSkippable.
nsCycleCollector_dispatchDeferredDeletion();
sScheduler.ActivateCCRunner();
sCCRunner =
IdleTaskRunner::Create(CCRunnerFired, "MaybePokeCC::CCRunnerFired",
kCCSkippableDelay.ToMilliseconds(),
kForgetSkippableSliceDuration.ToMilliseconds(),
true, [] { return sShuttingDown; });
if (!sCCRunner) {
sScheduler.InitCCRunnerStateMachine(
mozilla::CCGCScheduler::CCRunnerState::ReducePurple);
}
EnsureCCRunner(kCCSkippableDelay, kForgetSkippableSliceDuration);
}
}
@ -1930,16 +1900,6 @@ 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");

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

@ -108,8 +108,9 @@ 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();