зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 5 changesets (bug 1678416) for build bustages on CCGCScheduler.h. CLOSED TREE
Backed out changeset cc5f7ecb3922 (bug 1678416) Backed out changeset 61d9f960b80e (bug 1678416) Backed out changeset 882f7ebe33fe (bug 1678416) Backed out changeset e845a32a60bd (bug 1678416) Backed out changeset 85f35f5749a0 (bug 1678416)
This commit is contained in:
Родитель
62e7969d1a
Коммит
016881a724
|
@ -5,12 +5,10 @@
|
|||
#include "js/SliceBudget.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/MainThreadIdlePeriod.h"
|
||||
#include "nsCycleCollector.h"
|
||||
#include "nsJSEnvironment.h"
|
||||
|
||||
using mozilla::TimeDuration;
|
||||
|
||||
static const mozilla::TimeDuration kOneMinute =
|
||||
mozilla::TimeDuration::FromSeconds(60.0f);
|
||||
|
||||
|
@ -85,10 +83,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; }
|
||||
|
@ -99,19 +99,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
|
||||
|
@ -120,25 +110,24 @@ class CCGCScheduler {
|
|||
}
|
||||
|
||||
void NoteGCEnd() {
|
||||
mInIncrementalGC = false;
|
||||
mCCBlockStart = TimeStamp();
|
||||
mInIncrementalGC = false;
|
||||
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(); }
|
||||
|
@ -153,23 +142,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
|
||||
|
@ -178,23 +153,20 @@ class CCGCScheduler {
|
|||
mLastForgetSkippableCycleEndTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
void Shutdown() { mDidShutdown = true; }
|
||||
|
||||
// Scheduling
|
||||
|
||||
TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline,
|
||||
TimeStamp aNow) const {
|
||||
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 - aNow;
|
||||
TimeDuration budget = aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2
|
||||
: aDeadline - TimeStamp::Now();
|
||||
if (!mCCBlockStart) {
|
||||
return budget;
|
||||
}
|
||||
|
||||
TimeDuration blockedTime = aNow - mCCBlockStart;
|
||||
TimeDuration blockedTime = TimeStamp::Now() - mCCBlockStart;
|
||||
TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
|
||||
double percentOfBlockedTime =
|
||||
std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
|
||||
|
@ -268,10 +240,6 @@ class CCGCScheduler {
|
|||
}
|
||||
|
||||
bool ShouldScheduleCC() const {
|
||||
if (!mHasRunGC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
// Don't run consecutive CCs too often.
|
||||
|
@ -294,14 +262,7 @@ class CCGCScheduler {
|
|||
return IsCCNeeded(nsCycleCollector_suspectedCount(), 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;
|
||||
}
|
||||
|
||||
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const {
|
||||
bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) {
|
||||
int32_t numEarlyTimerFires =
|
||||
std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
|
||||
|
||||
|
@ -323,8 +284,7 @@ class CCGCScheduler {
|
|||
CleanupChildless,
|
||||
CleanupContentUnbinder,
|
||||
CleanupDeferred,
|
||||
StartCycleCollection,
|
||||
CycleCollecting
|
||||
CycleCollect
|
||||
};
|
||||
|
||||
enum CCRunnerYield { Continue, Yield };
|
||||
|
@ -348,26 +308,16 @@ class CCGCScheduler {
|
|||
CCRunnerForgetSkippableRemoveChildless mRemoveChildless;
|
||||
};
|
||||
|
||||
void InitCCRunnerStateMachine(CCRunnerState initialState) {
|
||||
void ActivateCCRunner() {
|
||||
MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive);
|
||||
mCCRunnerState = initialState;
|
||||
if (initialState == CCRunnerState::ReducePurple) {
|
||||
mCCDelay = kCCDelay;
|
||||
mCCRunnerEarlyFireCount = 0;
|
||||
} else if (initialState == CCRunnerState::CycleCollecting) {
|
||||
// Nothing needed.
|
||||
} else {
|
||||
MOZ_CRASH("Invalid initial state");
|
||||
}
|
||||
mCCRunnerState = CCRunnerState::ReducePurple;
|
||||
mCCDelay = kCCDelay;
|
||||
mCCRunnerEarlyFireCount = 0;
|
||||
}
|
||||
|
||||
void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; }
|
||||
|
||||
CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline, uint32_t aSuspected) {
|
||||
if (mDidShutdown) {
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
}
|
||||
|
||||
if (mCCRunnerState == CCRunnerState::Inactive) {
|
||||
// When we cancel a cycle, there may have been a final ForgetSkippable.
|
||||
return {CCRunnerAction::StopRunning, Yield};
|
||||
|
@ -376,29 +326,18 @@ class CCGCScheduler {
|
|||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
if (InIncrementalGC()) {
|
||||
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);
|
||||
}
|
||||
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) < kMaxCCLockedoutTime) {
|
||||
if (GetCCBlockedTime(now).value() < kMaxCCLockedoutTime) {
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
|
@ -412,8 +351,6 @@ class CCGCScheduler {
|
|||
switch (mCCRunnerState) {
|
||||
case CCRunnerState::ReducePurple:
|
||||
case CCRunnerState::CleanupDeferred:
|
||||
case CCRunnerState::StartCycleCollection:
|
||||
case CCRunnerState::CycleCollecting:
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -478,7 +415,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::StartCycleCollection;
|
||||
mCCRunnerState = CCRunnerState::CycleCollect;
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
|
@ -486,7 +423,7 @@ class CCGCScheduler {
|
|||
|
||||
// The deadline passed, so go straight to CC in the next slice.
|
||||
if (now >= aDeadline) {
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
mCCRunnerState = CCRunnerState::CycleCollect;
|
||||
return {CCRunnerAction::None, Yield};
|
||||
}
|
||||
|
||||
|
@ -500,7 +437,7 @@ class CCGCScheduler {
|
|||
|
||||
// Our efforts to avoid a CC have failed. Let the timer fire once more
|
||||
// to trigger a CC.
|
||||
mCCRunnerState = CCRunnerState::StartCycleCollection;
|
||||
mCCRunnerState = CCRunnerState::CycleCollect;
|
||||
if (now >= aDeadline) {
|
||||
// The deadline passed, go straight to CC in the next slice.
|
||||
return {CCRunnerAction::None, Yield};
|
||||
|
@ -508,22 +445,14 @@ class CCGCScheduler {
|
|||
|
||||
return {CCRunnerAction::CleanupDeferred, Yield};
|
||||
|
||||
// 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:
|
||||
// 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::CycleCollecting;
|
||||
[[fallthrough]];
|
||||
|
||||
// CycleCollecting: continue running slices until done.
|
||||
case CCRunnerState::CycleCollecting:
|
||||
mCCRunnerState = CCRunnerState::Inactive;
|
||||
return {CCRunnerAction::CycleCollect, Yield};
|
||||
|
||||
default:
|
||||
|
@ -554,8 +483,8 @@ class CCGCScheduler {
|
|||
(endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
|
||||
uint32_t frequencyPerMinute =
|
||||
uint32_t(mForgetSkippableCounter / duration);
|
||||
mozilla::Telemetry::Accumulate(
|
||||
mozilla::Telemetry::FORGET_SKIPPABLE_FREQUENCY, frequencyPerMinute);
|
||||
Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
|
||||
frequencyPerMinute);
|
||||
mForgetSkippableCounter = 0;
|
||||
mForgetSkippableFrequencyStartTime = aStartTimeStamp;
|
||||
}
|
||||
|
@ -566,7 +495,6 @@ class CCGCScheduler {
|
|||
return BudgetFromDuration(budgetTime);
|
||||
}
|
||||
|
||||
private:
|
||||
// State
|
||||
|
||||
// An incremental GC is in progress, which blocks the CC from running for its
|
||||
|
@ -576,9 +504,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;
|
||||
|
@ -588,23 +513,11 @@ 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);
|
||||
|
|
|
@ -94,6 +94,7 @@ 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;
|
||||
|
||||
|
@ -101,11 +102,19 @@ 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
|
||||
|
@ -284,10 +293,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;
|
||||
|
||||
|
@ -320,7 +337,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);
|
||||
|
@ -1096,10 +1113,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);
|
||||
}
|
||||
|
||||
|
@ -1313,11 +1330,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(),
|
||||
|
@ -1374,11 +1390,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);
|
||||
|
@ -1469,6 +1484,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());
|
||||
|
@ -1478,6 +1513,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()) {
|
||||
|
@ -1488,33 +1525,40 @@ 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(kICCIntersliceDelay, kIdleICCSliceBudget);
|
||||
// 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");
|
||||
|
@ -1536,15 +1580,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(
|
||||
|
@ -1614,6 +1658,10 @@ void ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) {
|
|||
}
|
||||
|
||||
static bool CCRunnerFired(TimeStamp aDeadline) {
|
||||
if (sDidShutdown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool didDoWork = false;
|
||||
|
||||
using CCRunnerAction = CCGCScheduler::CCRunnerAction;
|
||||
|
@ -1670,23 +1718,6 @@ 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);
|
||||
|
@ -1707,7 +1738,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;
|
||||
|
@ -1725,8 +1756,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1804,7 +1841,7 @@ void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj,
|
|||
JS::Zone* zone = JS::GetObjectZone(aObj);
|
||||
CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
|
||||
} else if (aReason != JS::GCReason::CC_WAITING) {
|
||||
sScheduler.SetNeedsFullGC();
|
||||
sNeedsFullGC = true;
|
||||
}
|
||||
|
||||
if (sGCTimer || sInterSliceGCRunner) {
|
||||
|
@ -1813,9 +1850,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;
|
||||
}
|
||||
|
||||
|
@ -1845,7 +1890,7 @@ void nsJSContext::PokeShrinkingGC() {
|
|||
|
||||
// static
|
||||
void nsJSContext::MaybePokeCC() {
|
||||
if (sCCRunner || sShuttingDown) {
|
||||
if (sCCRunner || sICCRunner || !sHasRunGC || sShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1853,11 +1898,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; });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1901,6 +1947,16 @@ 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");
|
||||
|
@ -1937,6 +1993,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_) {
|
||||
|
@ -1955,7 +2016,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress,
|
|||
}
|
||||
|
||||
if (!aDesc.isZone_) {
|
||||
sScheduler.SetNeedsFullGC(false);
|
||||
sNeedsFullGC = false;
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::GC_IN_PROGRESS_MS,
|
||||
|
@ -2022,13 +2083,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();
|
||||
|
@ -2324,7 +2392,7 @@ void mozilla::dom::ShutdownJSEnvironment() {
|
|||
KillTimers();
|
||||
|
||||
sShuttingDown = true;
|
||||
sScheduler.Shutdown();
|
||||
sDidShutdown = true;
|
||||
}
|
||||
|
||||
AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -11,28 +11,28 @@
|
|||
namespace mozilla {
|
||||
|
||||
already_AddRefed<IdleTaskRunner> IdleTaskRunner::Create(
|
||||
const CallbackType& aCallback, const char* aRunnableName,
|
||||
uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
|
||||
const CallbackType& aCallback, const char* aRunnableName, uint32_t aDelay,
|
||||
int64_t aBudget, bool aRepeating,
|
||||
const MayStopProcessingCallbackType& aMayStopProcessing) {
|
||||
if (aMayStopProcessing && aMayStopProcessing()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<IdleTaskRunner> runner =
|
||||
new IdleTaskRunner(aCallback, aRunnableName, aMaxDelay, aNonIdleBudget,
|
||||
aRepeating, aMayStopProcessing);
|
||||
new IdleTaskRunner(aCallback, aRunnableName, aDelay, aBudget, aRepeating,
|
||||
aMayStopProcessing);
|
||||
runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
|
||||
return runner.forget();
|
||||
}
|
||||
|
||||
IdleTaskRunner::IdleTaskRunner(
|
||||
const CallbackType& aCallback, const char* aRunnableName,
|
||||
uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
|
||||
const CallbackType& aCallback, const char* aRunnableName, uint32_t aDelay,
|
||||
int64_t aBudget, bool aRepeating,
|
||||
const MayStopProcessingCallbackType& aMayStopProcessing)
|
||||
: CancelableIdleRunnable(aRunnableName),
|
||||
mCallback(aCallback),
|
||||
mDelay(aMaxDelay),
|
||||
mBudget(TimeDuration::FromMilliseconds(aNonIdleBudget)),
|
||||
mDelay(aDelay),
|
||||
mBudget(TimeDuration::FromMilliseconds(aBudget)),
|
||||
mRepeating(aRepeating),
|
||||
mTimerActive(false),
|
||||
mMayStopProcessing(aMayStopProcessing),
|
||||
|
@ -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.
|
||||
|
@ -141,8 +136,8 @@ void IdleTaskRunner::Schedule(bool aAllowIdleDispatch) {
|
|||
// We weren't allowed to do idle dispatch immediately, do it after a
|
||||
// short timeout.
|
||||
mScheduleTimer->InitWithNamedFuncCallback(
|
||||
ScheduleTimedOut, this, 16 /* ms */,
|
||||
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, mName);
|
||||
ScheduleTimedOut, this, 16, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
|
||||
mName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 CancelableIdleRunnable {
|
||||
public:
|
||||
// Return true if some meaningful work was done.
|
||||
|
@ -29,34 +30,23 @@ class IdleTaskRunner final : public CancelableIdleRunnable {
|
|||
using MayStopProcessingCallbackType = std::function<bool()>;
|
||||
|
||||
public:
|
||||
// An IdleTaskRunner will attempt to run in idle time, with a budget computed
|
||||
// based on a (capped) estimate for how much idle time is available. If there
|
||||
// is no idle time within `aMaxDelay` ms, it will fall back to running using
|
||||
// a specified `aNonIdleBudget`.
|
||||
static already_AddRefed<IdleTaskRunner> Create(
|
||||
const CallbackType& aCallback, const char* aRunnableName,
|
||||
uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
|
||||
const CallbackType& aCallback, const char* aRunnableName, uint32_t aDelay,
|
||||
int64_t aBudget, bool aRepeating,
|
||||
const MayStopProcessingCallbackType& aMayStopProcessing);
|
||||
|
||||
NS_IMETHOD Run() override;
|
||||
|
||||
// (Used by the task triggering code.) Record the end of the current idle
|
||||
// period, or null if not running during idle time.
|
||||
void SetDeadline(mozilla::TimeStamp aDeadline) override;
|
||||
|
||||
void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override;
|
||||
|
||||
// Update the non-idle time budgeted for this callback. This really only
|
||||
// makes sense for a repeating runner.
|
||||
void SetBudget(int64_t aBudget);
|
||||
|
||||
nsresult Cancel() override;
|
||||
void Schedule(bool aAllowIdleDispatch);
|
||||
|
||||
private:
|
||||
explicit IdleTaskRunner(
|
||||
const CallbackType& aCallback, const char* aRunnableName,
|
||||
uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
|
||||
const CallbackType& aCallback, const char* aRunnableName, uint32_t aDelay,
|
||||
int64_t aBudget, bool aRepeating,
|
||||
const MayStopProcessingCallbackType& aMayStopProcessing);
|
||||
~IdleTaskRunner();
|
||||
void CancelTimer();
|
||||
|
@ -65,19 +55,9 @@ class IdleTaskRunner final : public CancelableIdleRunnable {
|
|||
nsCOMPtr<nsITimer> mTimer;
|
||||
nsCOMPtr<nsITimer> mScheduleTimer;
|
||||
CallbackType mCallback;
|
||||
|
||||
// Wait this long for idle time before giving up and running a non-idle
|
||||
// callback.
|
||||
uint32_t mDelay;
|
||||
|
||||
// If running during idle time, the expected end of the current idle period.
|
||||
// The null timestamp when the run is triggered by aMaxDelay instead of idle.
|
||||
TimeStamp mDeadline;
|
||||
|
||||
// The expected amount of time the callback will run for, when not running
|
||||
// during idle time.
|
||||
TimeDuration mBudget;
|
||||
|
||||
bool mRepeating;
|
||||
bool mTimerActive;
|
||||
MayStopProcessingCallbackType mMayStopProcessing;
|
||||
|
|
Загрузка…
Ссылка в новой задаче