2021-07-09 10:14:11 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "CCGCScheduler.h"
|
|
|
|
|
|
|
|
#include "mozilla/StaticPrefs_javascript.h"
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
|
|
|
|
// These definitions must match those in nsJSEnvironment.cpp
|
|
|
|
TimeStamp CCGCScheduler::Now() { return TimeStamp::Now(); }
|
|
|
|
|
|
|
|
uint32_t CCGCScheduler::SuspectedCCObjects() {
|
|
|
|
return nsCycleCollector_suspectedCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCGCScheduler::FullGCTimerFired(nsITimer* aTimer) {
|
|
|
|
KillFullGCTimer();
|
|
|
|
|
|
|
|
RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
|
|
|
|
CCGCScheduler::MayGCNow(JS::GCReason::FULL_GC_TIMER);
|
|
|
|
if (mbPromise) {
|
|
|
|
mbPromise->Then(
|
|
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
|
|
[](bool aIgnored) {
|
|
|
|
nsJSContext::GarbageCollectNow(JS::GCReason::FULL_GC_TIMER,
|
|
|
|
nsJSContext::IncrementalGC);
|
|
|
|
},
|
|
|
|
[](mozilla::ipc::ResponseRejectReason r) {
|
|
|
|
// do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// nsJSEnvironmentObserver observes the user-interaction-inactive notifications
|
|
|
|
// and triggers a shrinking a garbage collection if the user is still inactive
|
|
|
|
// after NS_SHRINKING_GC_DELAY ms later, if the appropriate pref is set.
|
|
|
|
|
|
|
|
void CCGCScheduler::ShrinkingGCTimerFired(nsITimer* aTimer) {
|
|
|
|
KillShrinkingGCTimer();
|
|
|
|
|
|
|
|
RefPtr<MayGCPromise> mbPromise = MayGCNow(JS::GCReason::USER_INACTIVE);
|
|
|
|
if (mbPromise) {
|
|
|
|
mbPromise->Then(
|
|
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
|
|
[](bool aIgnored) {
|
|
|
|
if (!sUserIsActive) {
|
|
|
|
sIsCompactingOnUserInactive = true;
|
|
|
|
nsJSContext::GarbageCollectNow(JS::GCReason::USER_INACTIVE,
|
|
|
|
nsJSContext::IncrementalGC,
|
|
|
|
nsJSContext::ShrinkingGC);
|
|
|
|
} else {
|
|
|
|
using mozilla::ipc::IdleSchedulerChild;
|
|
|
|
IdleSchedulerChild* child =
|
|
|
|
IdleSchedulerChild::GetMainThreadIdleScheduler();
|
|
|
|
if (child) {
|
|
|
|
child->DoneGC();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[](mozilla::ipc::ResponseRejectReason r) {});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCGCScheduler::GCRunnerFired(TimeStamp aDeadline) {
|
|
|
|
MOZ_ASSERT(!mDidShutdown, "GCRunner still alive during shutdown");
|
|
|
|
|
|
|
|
GCRunnerStep step = GetNextGCRunnerAction(aDeadline);
|
|
|
|
switch (step.mAction) {
|
|
|
|
case GCRunnerAction::None:
|
|
|
|
MOZ_CRASH("Unexpected GCRunnerAction");
|
|
|
|
|
|
|
|
case GCRunnerAction::WaitToMajorGC: {
|
|
|
|
RefPtr<CCGCScheduler::MayGCPromise> mbPromise =
|
|
|
|
CCGCScheduler::MayGCNow(step.mReason);
|
|
|
|
if (!mbPromise || mbPromise->IsResolved()) {
|
|
|
|
// Only use the promise if it's not resolved yet, otherwise fall through
|
|
|
|
// and begin the GC in the current idle time with our current deadline.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
KillGCRunner();
|
|
|
|
mbPromise->Then(
|
|
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
|
|
[this](bool aIgnored) {
|
|
|
|
if (!NoteReadyForMajorGC()) {
|
|
|
|
return; // Another GC completed while waiting.
|
|
|
|
}
|
|
|
|
// If a new runner was started, recreate it with a 0 delay. The new
|
|
|
|
// runner will continue in idle time.
|
|
|
|
KillGCRunner();
|
|
|
|
mGCRunner = IdleTaskRunner::Create(
|
|
|
|
[this](TimeStamp aDeadline) {
|
|
|
|
return GCRunnerFired(aDeadline);
|
|
|
|
},
|
|
|
|
"GCRunnerFired", 0,
|
|
|
|
StaticPrefs::javascript_options_gc_delay_interslice(),
|
|
|
|
int64_t(mActiveIntersliceGCBudget.ToMilliseconds()), true,
|
|
|
|
[this] { return mDidShutdown; });
|
|
|
|
},
|
|
|
|
[](mozilla::ipc::ResponseRejectReason r) {});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GCRunnerAction::StartMajorGC:
|
|
|
|
case GCRunnerAction::GCSlice:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run a GC slice, possibly the first one of a major GC.
|
|
|
|
|
|
|
|
MOZ_ASSERT(mActiveIntersliceGCBudget);
|
|
|
|
TimeStamp startTimeStamp = TimeStamp::Now();
|
|
|
|
TimeDuration budget = ComputeInterSliceGCBudget(aDeadline, startTimeStamp);
|
|
|
|
TimeDuration duration = mGCUnnotifiedTotalTime;
|
|
|
|
nsJSContext::GarbageCollectNow(step.mReason, nsJSContext::IncrementalGC,
|
|
|
|
nsJSContext::NonShrinkingGC,
|
|
|
|
budget.ToMilliseconds());
|
|
|
|
|
|
|
|
mGCUnnotifiedTotalTime = TimeDuration();
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
|
|
TimeDuration sliceDuration = now - startTimeStamp;
|
|
|
|
duration += sliceDuration;
|
|
|
|
if (duration.ToSeconds()) {
|
|
|
|
TimeDuration idleDuration;
|
|
|
|
if (!aDeadline.IsNull()) {
|
|
|
|
if (aDeadline < now) {
|
|
|
|
// This slice overflowed the idle period.
|
|
|
|
idleDuration = aDeadline - startTimeStamp;
|
|
|
|
} else {
|
|
|
|
// Note, we don't want to use duration here, since it may contain
|
|
|
|
// data also from JS engine triggered GC slices.
|
|
|
|
idleDuration = sliceDuration;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t percent =
|
|
|
|
uint32_t(idleDuration.ToSeconds() / duration.ToSeconds() * 100);
|
|
|
|
Telemetry::Accumulate(Telemetry::GC_SLICE_DURING_IDLE, percent);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the GC doesn't have any more work to do on the foreground thread (and
|
|
|
|
// e.g. is waiting for background sweeping to finish) then return false to
|
|
|
|
// make IdleTaskRunner postpone the next call a bit.
|
|
|
|
JSContext* cx = dom::danger::GetJSContext();
|
|
|
|
return JS::IncrementalGCHasForegroundWork(cx);
|
|
|
|
}
|
|
|
|
|
|
|
|
RefPtr<CCGCScheduler::MayGCPromise> CCGCScheduler::MayGCNow(
|
|
|
|
JS::GCReason reason) {
|
|
|
|
using namespace mozilla::ipc;
|
|
|
|
|
|
|
|
// We ask the parent if we should GC for GCs that aren't too timely,
|
|
|
|
// with the exception of MEM_PRESSURE, in that case we ask the parent
|
|
|
|
// because GCing on too many processes at the same time when under
|
|
|
|
// memory pressure could be a very bad experience for the user.
|
|
|
|
switch (reason) {
|
|
|
|
case JS::GCReason::PAGE_HIDE:
|
|
|
|
case JS::GCReason::MEM_PRESSURE:
|
|
|
|
case JS::GCReason::USER_INACTIVE:
|
|
|
|
case JS::GCReason::FULL_GC_TIMER:
|
|
|
|
case JS::GCReason::CC_FINISHED: {
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
|
|
IdleSchedulerChild* child =
|
|
|
|
IdleSchedulerChild::GetMainThreadIdleScheduler();
|
|
|
|
if (child) {
|
|
|
|
return child->MayGCNow();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The parent process doesn't ask IdleSchedulerParent if it can GC.
|
|
|
|
// TODO: but it should ask.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return MayGCPromise::CreateAndResolve(true, __func__);
|
|
|
|
}
|
|
|
|
|
2021-07-09 10:14:12 +03:00
|
|
|
void CCGCScheduler::RunNextCollectorTimer(JS::GCReason aReason,
|
|
|
|
mozilla::TimeStamp aDeadline) {
|
|
|
|
if (mDidShutdown) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// When we're in an incremental GC, we should always have an sGCRunner, so do
|
|
|
|
// not check CC timers. The CC timers won't do anything during a GC.
|
|
|
|
MOZ_ASSERT_IF(InIncrementalGC(), mGCRunner);
|
|
|
|
|
|
|
|
RefPtr<IdleTaskRunner> runner;
|
|
|
|
if (mGCRunner) {
|
|
|
|
SetWantMajorGC(aReason);
|
|
|
|
runner = mGCRunner;
|
|
|
|
} else if (mCCRunner) {
|
|
|
|
runner = mCCRunner;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (runner) {
|
|
|
|
runner->SetIdleDeadline(aDeadline);
|
|
|
|
runner->Run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 10:14:11 +03:00
|
|
|
void CCGCScheduler::PokeShrinkingGC() {
|
|
|
|
if (mShrinkingGCTimer || mDidShutdown) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_NewTimerWithFuncCallback(
|
|
|
|
&mShrinkingGCTimer,
|
|
|
|
[](nsITimer* aTimer, void* aClosure) {
|
|
|
|
static_cast<CCGCScheduler*>(aClosure)->ShrinkingGCTimerFired(aTimer);
|
|
|
|
},
|
|
|
|
this, StaticPrefs::javascript_options_compact_on_user_inactive_delay(),
|
|
|
|
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "ShrinkingGCTimerFired");
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCGCScheduler::PokeFullGC() {
|
|
|
|
if (!mFullGCTimer && !mDidShutdown) {
|
|
|
|
NS_NewTimerWithFuncCallback(
|
|
|
|
&mFullGCTimer,
|
|
|
|
[](nsITimer* aTimer, void* aClosure) {
|
|
|
|
static_cast<CCGCScheduler*>(aClosure)->FullGCTimerFired(aTimer);
|
|
|
|
},
|
|
|
|
this, StaticPrefs::javascript_options_gc_delay_full(),
|
|
|
|
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "FullGCTimerFired");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCGCScheduler::KillShrinkingGCTimer() {
|
|
|
|
if (mShrinkingGCTimer) {
|
|
|
|
mShrinkingGCTimer->Cancel();
|
|
|
|
NS_RELEASE(mShrinkingGCTimer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCGCScheduler::KillFullGCTimer() {
|
|
|
|
if (mFullGCTimer) {
|
|
|
|
mFullGCTimer->Cancel();
|
|
|
|
NS_RELEASE(mFullGCTimer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCGCScheduler::KillGCRunner() {
|
|
|
|
if (mGCRunner) {
|
|
|
|
mGCRunner->Cancel();
|
|
|
|
mGCRunner = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 10:14:11 +03:00
|
|
|
void CCGCScheduler::EnsureCCRunner(TimeDuration aDelay, TimeDuration aBudget) {
|
|
|
|
MOZ_ASSERT(!mDidShutdown);
|
|
|
|
|
|
|
|
if (!mCCRunner) {
|
|
|
|
mCCRunner = IdleTaskRunner::Create(
|
|
|
|
CCRunnerFired, "EnsureCCRunner::CCRunnerFired", 0,
|
|
|
|
aDelay.ToMilliseconds(), aBudget.ToMilliseconds(), true,
|
|
|
|
[this] { return mDidShutdown; });
|
|
|
|
} else {
|
|
|
|
mCCRunner->SetMinimumUsefulBudget(aBudget.ToMilliseconds());
|
|
|
|
nsIEventTarget* target = mozilla::GetCurrentEventTarget();
|
|
|
|
if (target) {
|
|
|
|
mCCRunner->SetTimer(aDelay.ToMilliseconds(), target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CCGCScheduler::KillCCRunner() {
|
|
|
|
UnblockCC();
|
|
|
|
DeactivateCCRunner();
|
|
|
|
if (mCCRunner) {
|
|
|
|
mCCRunner->Cancel();
|
|
|
|
mCCRunner = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 10:14:12 +03:00
|
|
|
void CCGCScheduler::KillAllTimersAndRunners() {
|
|
|
|
KillShrinkingGCTimer();
|
|
|
|
KillCCRunner();
|
|
|
|
KillFullGCTimer();
|
|
|
|
KillGCRunner();
|
|
|
|
}
|
|
|
|
|
2021-07-09 10:14:11 +03:00
|
|
|
} // namespace mozilla
|