зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
630f77d442
Коммит
bd6330b7a4
|
@ -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();
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче