|
|
|
|
@@ -56,8 +56,6 @@
|
|
|
|
|
#include "nsGlobalWindow.h"
|
|
|
|
|
#include "nsScriptNameSpaceManager.h"
|
|
|
|
|
#include "mozilla/AutoRestore.h"
|
|
|
|
|
#include "mozilla/MainThreadIdlePeriod.h"
|
|
|
|
|
#include "mozilla/StaticPtr.h"
|
|
|
|
|
#include "mozilla/dom/DOMException.h"
|
|
|
|
|
#include "mozilla/dom/DOMExceptionBinding.h"
|
|
|
|
|
#include "mozilla/dom/ErrorEvent.h"
|
|
|
|
|
@@ -65,7 +63,7 @@
|
|
|
|
|
#include "nsAXPCNativeCallContext.h"
|
|
|
|
|
#include "mozilla/CycleCollectedJSRuntime.h"
|
|
|
|
|
#include "mozilla/SystemGroup.h"
|
|
|
|
|
#include "nsRefreshDriver.h"
|
|
|
|
|
|
|
|
|
|
#include "nsJSPrincipals.h"
|
|
|
|
|
|
|
|
|
|
#ifdef XP_MACOSX
|
|
|
|
|
@@ -113,24 +111,21 @@ const size_t gStackSize = 8192;
|
|
|
|
|
// Maximum amount of time that should elapse between incremental GC slices
|
|
|
|
|
#define NS_INTERSLICE_GC_DELAY 100 // ms
|
|
|
|
|
|
|
|
|
|
// If we haven't painted in 100ms, or we're in e10s parent process and
|
|
|
|
|
// user isn't active, we allow for a longer GC budget.
|
|
|
|
|
#define NS_INTERSLICE_GC_BUDGET 40 // ms
|
|
|
|
|
|
|
|
|
|
// The amount of time we wait between a request to CC (after GC ran)
|
|
|
|
|
// and doing the actual CC.
|
|
|
|
|
#define NS_CC_DELAY 6000 // ms
|
|
|
|
|
|
|
|
|
|
#define NS_CC_SKIPPABLE_DELAY 250 // ms
|
|
|
|
|
|
|
|
|
|
// ForgetSkippable is usually fast, so we can use small budgets.
|
|
|
|
|
// This isn't a real budget but a hint to CollectorRunner whether there
|
|
|
|
|
// is enough time to call ForgetSkippable.
|
|
|
|
|
static const int64_t kForgetSkippableSliceDuration = 2;
|
|
|
|
|
|
|
|
|
|
// Maximum amount of time that should elapse between incremental CC slices
|
|
|
|
|
static const int64_t kICCIntersliceDelay = 32; // ms
|
|
|
|
|
|
|
|
|
|
// Time budget for an incremental CC slice when using timer to run it.
|
|
|
|
|
// Time budget for an incremental CC slice
|
|
|
|
|
static const int64_t kICCSliceBudget = 5; // ms
|
|
|
|
|
// Minimum budget for an incremental CC slice when using idle time to run it.
|
|
|
|
|
static const int64_t kIdleICCSliceBudget = 3; // ms
|
|
|
|
|
|
|
|
|
|
// Maximum total duration for an ICC
|
|
|
|
|
static const uint32_t kMaxICCDuration = 2000; // ms
|
|
|
|
|
@@ -149,16 +144,14 @@ static const uint32_t kMaxICCDuration = 2000; // ms
|
|
|
|
|
// Large value used to specify that a script should run essentially forever
|
|
|
|
|
#define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
|
|
|
|
|
|
|
|
|
|
class CollectorRunner;
|
|
|
|
|
|
|
|
|
|
// if you add statics here, add them to the list in StartupJSEnvironment
|
|
|
|
|
|
|
|
|
|
static nsITimer *sGCTimer;
|
|
|
|
|
static nsITimer *sShrinkingGCTimer;
|
|
|
|
|
static StaticRefPtr<CollectorRunner> sCCRunner;
|
|
|
|
|
static StaticRefPtr<CollectorRunner> sICCRunner;
|
|
|
|
|
static nsITimer *sCCTimer;
|
|
|
|
|
static nsITimer *sICCTimer;
|
|
|
|
|
static nsITimer *sFullGCTimer;
|
|
|
|
|
static StaticRefPtr<CollectorRunner> sInterSliceGCRunner;
|
|
|
|
|
static nsITimer *sInterSliceGCTimer;
|
|
|
|
|
|
|
|
|
|
static TimeStamp sLastCCEndTime;
|
|
|
|
|
|
|
|
|
|
@@ -183,7 +176,7 @@ static uint32_t sCCollectedZonesWaitingForGC;
|
|
|
|
|
static uint32_t sLikelyShortLivingObjectsNeedingGC;
|
|
|
|
|
static bool sPostGCEventsToConsole;
|
|
|
|
|
static bool sPostGCEventsToObserver;
|
|
|
|
|
static int32_t sCCRunnerFireCount = 0;
|
|
|
|
|
static int32_t sCCTimerFireCount = 0;
|
|
|
|
|
static uint32_t sMinForgetSkippableTime = UINT32_MAX;
|
|
|
|
|
static uint32_t sMaxForgetSkippableTime = 0;
|
|
|
|
|
static uint32_t sTotalForgetSkippableTime = 0;
|
|
|
|
|
@@ -195,6 +188,7 @@ static bool sNeedsFullCC = false;
|
|
|
|
|
static bool sNeedsFullGC = false;
|
|
|
|
|
static bool sNeedsGCAfterCC = false;
|
|
|
|
|
static bool sIncrementalCC = false;
|
|
|
|
|
static bool sDidPaintAfterPreviousICCSlice = false;
|
|
|
|
|
static int32_t sActiveIntersliceGCBudget = 0; // ms;
|
|
|
|
|
static nsScriptNameSpaceManager *gNameSpaceManager;
|
|
|
|
|
|
|
|
|
|
@@ -229,178 +223,6 @@ static bool sIsCompactingOnUserInactive = false;
|
|
|
|
|
static int32_t sExpensiveCollectorPokes = 0;
|
|
|
|
|
static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5;
|
|
|
|
|
|
|
|
|
|
// Return true if some meaningful work was done.
|
|
|
|
|
typedef bool (*CollectorRunnerCallback) (TimeStamp aDeadline, void* aData);
|
|
|
|
|
|
|
|
|
|
// Repeating callback runner for CC and GC.
|
|
|
|
|
class CollectorRunner final : public IdleRunnable
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static already_AddRefed<CollectorRunner>
|
|
|
|
|
Create(CollectorRunnerCallback aCallback, uint32_t aDelay,
|
|
|
|
|
int64_t aBudget, bool aRepeating, void* aData = nullptr)
|
|
|
|
|
{
|
|
|
|
|
RefPtr<CollectorRunner> runner =
|
|
|
|
|
new CollectorRunner(aCallback, aDelay, aBudget, aRepeating, aData);
|
|
|
|
|
runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
|
|
|
|
|
return runner.forget();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NS_IMETHOD Run() override
|
|
|
|
|
{
|
|
|
|
|
if (!mCallback) {
|
|
|
|
|
return NS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Deadline is null when called from timer.
|
|
|
|
|
bool deadLineWasNull = mDeadline.IsNull();
|
|
|
|
|
bool didRun = false;
|
|
|
|
|
if (deadLineWasNull || ((TimeStamp::Now() + mBudget) < mDeadline)) {
|
|
|
|
|
CancelTimer();
|
|
|
|
|
didRun = mCallback(mDeadline, mData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mCallback && (mRepeating || !didRun)) {
|
|
|
|
|
// If we didn't do meaningful work, don't schedule using immediate
|
|
|
|
|
// idle dispatch, since that could lead to a loop until the idle
|
|
|
|
|
// period ends.
|
|
|
|
|
Schedule(didRun);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
TimedOut(nsITimer* aTimer, void* aClosure)
|
|
|
|
|
{
|
|
|
|
|
RefPtr<CollectorRunner> runnable = static_cast<CollectorRunner*>(aClosure);
|
|
|
|
|
runnable->Run();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetDeadline(mozilla::TimeStamp aDeadline) override
|
|
|
|
|
{
|
|
|
|
|
mDeadline = aDeadline;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override
|
|
|
|
|
{
|
|
|
|
|
if (mTimerActive) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mTarget = aTarget;
|
|
|
|
|
if (!mTimer) {
|
|
|
|
|
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
|
|
|
} else {
|
|
|
|
|
mTimer->Cancel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mTimer) {
|
|
|
|
|
mTimer->SetTarget(mTarget);
|
|
|
|
|
mTimer->InitWithFuncCallback(TimedOut, this, aDelay,
|
|
|
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
|
|
|
mTimerActive = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsresult Cancel() override
|
|
|
|
|
{
|
|
|
|
|
CancelTimer();
|
|
|
|
|
mTimer = nullptr;
|
|
|
|
|
mScheduleTimer = nullptr;
|
|
|
|
|
mCallback = nullptr;
|
|
|
|
|
return NS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ScheduleTimedOut(nsITimer* aTimer, void* aClosure)
|
|
|
|
|
{
|
|
|
|
|
RefPtr<CollectorRunner> runnable = static_cast<CollectorRunner*>(aClosure);
|
|
|
|
|
runnable->Schedule(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Schedule(bool aAllowIdleDispatch)
|
|
|
|
|
{
|
|
|
|
|
if (!mCallback) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sShuttingDown) {
|
|
|
|
|
Cancel();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mDeadline = TimeStamp();
|
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
|
|
|
TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now);
|
|
|
|
|
if (hint != now) {
|
|
|
|
|
// RefreshDriver is ticking, let it schedule the idle dispatch.
|
|
|
|
|
nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay);
|
|
|
|
|
// Ensure we get called at some point, even if RefreshDriver is stopped.
|
|
|
|
|
SetTimer(mDelay, mTarget);
|
|
|
|
|
} else {
|
|
|
|
|
// RefreshDriver doesn't seem to be running.
|
|
|
|
|
if (aAllowIdleDispatch) {
|
|
|
|
|
nsCOMPtr<nsIRunnable> runnable = this;
|
|
|
|
|
NS_IdleDispatchToCurrentThread(runnable.forget(), mDelay);
|
|
|
|
|
SetTimer(mDelay, mTarget);
|
|
|
|
|
} else {
|
|
|
|
|
if (!mScheduleTimer) {
|
|
|
|
|
mScheduleTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
|
|
|
if (!mScheduleTimer) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
mScheduleTimer->Cancel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We weren't allowed to do idle dispatch immediately, do it after a
|
|
|
|
|
// short timeout.
|
|
|
|
|
mScheduleTimer->InitWithFuncCallback(ScheduleTimedOut, this, 16,
|
|
|
|
|
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
explicit CollectorRunner(CollectorRunnerCallback aCallback,
|
|
|
|
|
uint32_t aDelay, int64_t aBudget,
|
|
|
|
|
bool aRepeating, void* aData)
|
|
|
|
|
: mCallback(aCallback), mDelay(aDelay)
|
|
|
|
|
, mBudget(TimeDuration::FromMilliseconds(aBudget))
|
|
|
|
|
, mRepeating(aRepeating), mTimerActive(false), mData(aData)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~CollectorRunner()
|
|
|
|
|
{
|
|
|
|
|
CancelTimer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CancelTimer()
|
|
|
|
|
{
|
|
|
|
|
nsRefreshDriver::CancelIdleRunnable(this);
|
|
|
|
|
if (mTimer) {
|
|
|
|
|
mTimer->Cancel();
|
|
|
|
|
}
|
|
|
|
|
if (mScheduleTimer) {
|
|
|
|
|
mScheduleTimer->Cancel();
|
|
|
|
|
}
|
|
|
|
|
mTimerActive = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
|
|
|
nsCOMPtr<nsITimer> mScheduleTimer;
|
|
|
|
|
nsCOMPtr<nsIEventTarget> mTarget;
|
|
|
|
|
CollectorRunnerCallback mCallback;
|
|
|
|
|
uint32_t mDelay;
|
|
|
|
|
TimeStamp mDeadline;
|
|
|
|
|
TimeDuration mBudget;
|
|
|
|
|
bool mRepeating;
|
|
|
|
|
bool mTimerActive;
|
|
|
|
|
void* mData;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const char*
|
|
|
|
|
ProcessNameForCollectorLog()
|
|
|
|
|
{
|
|
|
|
|
@@ -477,10 +299,10 @@ KillTimers()
|
|
|
|
|
{
|
|
|
|
|
nsJSContext::KillGCTimer();
|
|
|
|
|
nsJSContext::KillShrinkingGCTimer();
|
|
|
|
|
nsJSContext::KillCCRunner();
|
|
|
|
|
nsJSContext::KillICCRunner();
|
|
|
|
|
nsJSContext::KillCCTimer();
|
|
|
|
|
nsJSContext::KillICCTimer();
|
|
|
|
|
nsJSContext::KillFullGCTimer();
|
|
|
|
|
nsJSContext::KillInterSliceGCRunner();
|
|
|
|
|
nsJSContext::KillInterSliceGCTimer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we collected a substantial amount of cycles, poke the GC since more objects
|
|
|
|
|
@@ -1629,7 +1451,7 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
void
|
|
|
|
|
nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline)
|
|
|
|
|
nsJSContext::RunCycleCollectorSlice()
|
|
|
|
|
{
|
|
|
|
|
if (!NS_IsMainThread()) {
|
|
|
|
|
return;
|
|
|
|
|
@@ -1645,40 +1467,33 @@ nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline)
|
|
|
|
|
js::SliceBudget budget = js::SliceBudget::unlimited();
|
|
|
|
|
|
|
|
|
|
if (sIncrementalCC) {
|
|
|
|
|
int64_t baseBudget = kICCSliceBudget;
|
|
|
|
|
if (!aDeadline.IsNull()) {
|
|
|
|
|
baseBudget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gCCStats.mBeginTime.IsNull()) {
|
|
|
|
|
// If no CC is in progress, use the standard slice time.
|
|
|
|
|
budget = js::SliceBudget(js::TimeBudget(baseBudget));
|
|
|
|
|
budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget));
|
|
|
|
|
} else {
|
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
|
|
|
|
|
|
|
|
// Only run a limited slice if we're within the max running time.
|
|
|
|
|
uint32_t runningTime = TimeBetween(gCCStats.mBeginTime, now);
|
|
|
|
|
if (runningTime < kMaxICCDuration) {
|
|
|
|
|
const float maxSlice = MainThreadIdlePeriod::GetLongIdlePeriod();
|
|
|
|
|
|
|
|
|
|
// Try to make up for a delay in running this slice.
|
|
|
|
|
float sliceDelayMultiplier =
|
|
|
|
|
TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay;
|
|
|
|
|
float delaySliceBudget =
|
|
|
|
|
std::min(baseBudget * sliceDelayMultiplier, maxSlice);
|
|
|
|
|
float sliceDelayMultiplier = TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay;
|
|
|
|
|
float delaySliceBudget = kICCSliceBudget * sliceDelayMultiplier;
|
|
|
|
|
|
|
|
|
|
// Increase slice budgets up to |maxSlice| as we approach
|
|
|
|
|
// Increase slice budgets up to |maxLaterSlice| as we approach
|
|
|
|
|
// half way through the ICC, to avoid large sync CCs.
|
|
|
|
|
float percentToHalfDone = std::min(2.0f * runningTime / kMaxICCDuration, 1.0f);
|
|
|
|
|
float laterSliceBudget = maxSlice * percentToHalfDone;
|
|
|
|
|
const float maxLaterSlice = 40.0f;
|
|
|
|
|
float laterSliceBudget = maxLaterSlice * percentToHalfDone;
|
|
|
|
|
|
|
|
|
|
budget = js::SliceBudget(js::TimeBudget(std::max({delaySliceBudget,
|
|
|
|
|
laterSliceBudget, (float)baseBudget})));
|
|
|
|
|
laterSliceBudget, (float)kICCSliceBudget})));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsCycleCollector_collectSlice(budget);
|
|
|
|
|
nsCycleCollector_collectSlice(budget, sDidPaintAfterPreviousICCSlice);
|
|
|
|
|
sDidPaintAfterPreviousICCSlice = false;
|
|
|
|
|
|
|
|
|
|
gCCStats.FinishCycleCollectionSlice();
|
|
|
|
|
}
|
|
|
|
|
@@ -1714,11 +1529,11 @@ nsJSContext::GetMaxCCSliceTimeSinceClear()
|
|
|
|
|
return gCCStats.mMaxSliceTimeSinceClear;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
ICCRunnerFired(TimeStamp aDeadline, void* aData)
|
|
|
|
|
static void
|
|
|
|
|
ICCTimerFired(nsITimer* aTimer, void* aClosure)
|
|
|
|
|
{
|
|
|
|
|
if (sDidShutdown) {
|
|
|
|
|
return false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
|
|
|
|
|
@@ -1728,15 +1543,14 @@ ICCRunnerFired(TimeStamp aDeadline, void* aData)
|
|
|
|
|
PRTime now = PR_Now();
|
|
|
|
|
if (sCCLockedOutTime == 0) {
|
|
|
|
|
sCCLockedOutTime = now;
|
|
|
|
|
return false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
|
|
|
|
|
return false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsJSContext::RunCycleCollectorSlice(aDeadline);
|
|
|
|
|
return true;
|
|
|
|
|
nsJSContext::RunCycleCollectorSlice();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
@@ -1748,16 +1562,22 @@ nsJSContext::BeginCycleCollectionCallback()
|
|
|
|
|
gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime;
|
|
|
|
|
gCCStats.mSuspected = nsCycleCollector_suspectedCount();
|
|
|
|
|
|
|
|
|
|
KillCCRunner();
|
|
|
|
|
KillCCTimer();
|
|
|
|
|
|
|
|
|
|
gCCStats.RunForgetSkippable();
|
|
|
|
|
|
|
|
|
|
MOZ_ASSERT(!sICCRunner, "Tried to create a new ICC timer when one already existed.");
|
|
|
|
|
MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed.");
|
|
|
|
|
|
|
|
|
|
// 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 = CollectorRunner::Create(ICCRunnerFired, kICCIntersliceDelay,
|
|
|
|
|
kIdleICCSliceBudget, true);
|
|
|
|
|
CallCreateInstance("@mozilla.org/timer;1", &sICCTimer);
|
|
|
|
|
if (sICCTimer) {
|
|
|
|
|
sICCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
|
|
|
|
|
sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
|
|
|
|
|
kICCIntersliceDelay,
|
|
|
|
|
nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
|
|
|
|
|
"ICCTimerFired");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
|
|
|
|
|
@@ -1768,7 +1588,7 @@ nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
|
|
|
|
|
{
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
|
|
nsJSContext::KillICCRunner();
|
|
|
|
|
nsJSContext::KillICCTimer();
|
|
|
|
|
|
|
|
|
|
// Update timing information for the current slice before we log it, if
|
|
|
|
|
// we previously called PrepareForCycleCollectionSlice(). During shutdown
|
|
|
|
|
@@ -1916,24 +1736,16 @@ nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
|
bool
|
|
|
|
|
InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData)
|
|
|
|
|
void
|
|
|
|
|
InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
|
|
|
|
|
{
|
|
|
|
|
nsJSContext::KillInterSliceGCRunner();
|
|
|
|
|
MOZ_ASSERT(sActiveIntersliceGCBudget > 0);
|
|
|
|
|
int64_t budget = sActiveIntersliceGCBudget;
|
|
|
|
|
if (!aDeadline.IsNull()) {
|
|
|
|
|
budget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uintptr_t reason = reinterpret_cast<uintptr_t>(aData);
|
|
|
|
|
nsJSContext::GarbageCollectNow(aData ?
|
|
|
|
|
static_cast<JS::gcreason::Reason>(reason) :
|
|
|
|
|
JS::gcreason::INTER_SLICE_GC,
|
|
|
|
|
nsJSContext::KillInterSliceGCTimer();
|
|
|
|
|
int64_t budget = XRE_IsE10sParentProcess() && nsContentUtils::GetUserIsInteracting() && sActiveIntersliceGCBudget ?
|
|
|
|
|
sActiveIntersliceGCBudget : NS_INTERSLICE_GC_BUDGET;
|
|
|
|
|
nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
|
|
|
|
|
nsJSContext::IncrementalGC,
|
|
|
|
|
nsJSContext::NonShrinkingGC,
|
|
|
|
|
budget);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
|
@@ -1941,12 +1753,9 @@ void
|
|
|
|
|
GCTimerFired(nsITimer *aTimer, void *aClosure)
|
|
|
|
|
{
|
|
|
|
|
nsJSContext::KillGCTimer();
|
|
|
|
|
// Now start the actual GC after initial timer has fired.
|
|
|
|
|
sInterSliceGCRunner = CollectorRunner::Create(InterSliceGCRunnerFired,
|
|
|
|
|
NS_INTERSLICE_GC_DELAY,
|
|
|
|
|
sActiveIntersliceGCBudget,
|
|
|
|
|
false,
|
|
|
|
|
aClosure);
|
|
|
|
|
uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
|
|
|
|
|
nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
|
|
|
|
|
nsJSContext::IncrementalGC);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
|
@@ -1969,11 +1778,11 @@ ShouldTriggerCC(uint32_t aSuspected)
|
|
|
|
|
TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
CCRunnerFired(TimeStamp aDeadline, void* aData)
|
|
|
|
|
static void
|
|
|
|
|
CCTimerFired(nsITimer *aTimer, void *aClosure)
|
|
|
|
|
{
|
|
|
|
|
if (sDidShutdown) {
|
|
|
|
|
return false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t ccDelay = NS_CC_DELAY;
|
|
|
|
|
@@ -1982,53 +1791,48 @@ CCRunnerFired(TimeStamp aDeadline, void* aData)
|
|
|
|
|
|
|
|
|
|
PRTime now = PR_Now();
|
|
|
|
|
if (sCCLockedOutTime == 0) {
|
|
|
|
|
// Reset sCCRunnerFireCount so that we run forgetSkippable
|
|
|
|
|
// Reset sCCTimerFireCount so that we run forgetSkippable
|
|
|
|
|
// often enough before CC. Because of reduced ccDelay
|
|
|
|
|
// forgetSkippable will be called just a few times.
|
|
|
|
|
// NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
|
|
|
|
|
// forgetSkippable and CycleCollectNow eventually.
|
|
|
|
|
sCCRunnerFireCount = 0;
|
|
|
|
|
sCCTimerFireCount = 0;
|
|
|
|
|
sCCLockedOutTime = now;
|
|
|
|
|
return false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
|
|
|
|
|
return false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++sCCRunnerFireCount;
|
|
|
|
|
|
|
|
|
|
bool didDoWork = false;
|
|
|
|
|
++sCCTimerFireCount;
|
|
|
|
|
|
|
|
|
|
// 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 / NS_CC_SKIPPABLE_DELAY - 2, 1);
|
|
|
|
|
bool isLateTimerFire = sCCRunnerFireCount > numEarlyTimerFires;
|
|
|
|
|
bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires;
|
|
|
|
|
uint32_t suspected = nsCycleCollector_suspectedCount();
|
|
|
|
|
if (isLateTimerFire && ShouldTriggerCC(suspected)) {
|
|
|
|
|
if (sCCRunnerFireCount == numEarlyTimerFires + 1) {
|
|
|
|
|
if (sCCTimerFireCount == numEarlyTimerFires + 1) {
|
|
|
|
|
FireForgetSkippable(suspected, true);
|
|
|
|
|
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.
|
|
|
|
|
return didDoWork;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 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;
|
|
|
|
|
nsJSContext::RunCycleCollectorSlice();
|
|
|
|
|
}
|
|
|
|
|
} else if (((sPreviousSuspectedCount + 100) <= suspected) ||
|
|
|
|
|
(sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS)) {
|
|
|
|
|
// 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);
|
|
|
|
|
didDoWork = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isLateTimerFire) {
|
|
|
|
|
@@ -2037,10 +1841,8 @@ CCRunnerFired(TimeStamp aDeadline, void* aData)
|
|
|
|
|
// We have either just run the CC or decided we don't want to run the CC
|
|
|
|
|
// next time, so kill the timer.
|
|
|
|
|
sPreviousSuspectedCount = 0;
|
|
|
|
|
nsJSContext::KillCCRunner();
|
|
|
|
|
nsJSContext::KillCCTimer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return didDoWork;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
|
@@ -2087,13 +1889,13 @@ ReadyToTriggerExpensiveCollectorTimer()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check all of the various collector timers/runners and see if they are waiting to fire.
|
|
|
|
|
// For the synchronous collector timers/runners, sGCTimer and sCCRunner, we only want to
|
|
|
|
|
// trigger the collection occasionally, because they are expensive. The incremental collector
|
|
|
|
|
// timers, sInterSliceGCRunner and sICCRunner, are fast and need to be run many times, so
|
|
|
|
|
// Check all of the various collector timers and see if they are waiting to fire.
|
|
|
|
|
// For the synchronous collector timers, sGCTimer and sCCTimer, we only want to trigger
|
|
|
|
|
// the collection occasionally, because they are expensive. The incremental collector
|
|
|
|
|
// timers, sInterSliceGCTimer and sICCTimer, are fast and need to be run many times, so
|
|
|
|
|
// always run their corresponding timer.
|
|
|
|
|
|
|
|
|
|
// This does not check sFullGCTimer, as that's a more expensive collection we run
|
|
|
|
|
// This does not check sFullGCTimer, as that's an even more expensive collection we run
|
|
|
|
|
// on a long timer.
|
|
|
|
|
|
|
|
|
|
// static
|
|
|
|
|
@@ -2111,8 +1913,8 @@ nsJSContext::RunNextCollectorTimer()
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sInterSliceGCRunner) {
|
|
|
|
|
InterSliceGCRunnerFired(TimeStamp(), nullptr);
|
|
|
|
|
if (sInterSliceGCTimer) {
|
|
|
|
|
InterSliceGCTimerFired(nullptr, nullptr);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2120,15 +1922,15 @@ nsJSContext::RunNextCollectorTimer()
|
|
|
|
|
// anything if a GC is in progress.
|
|
|
|
|
MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out.");
|
|
|
|
|
|
|
|
|
|
if (sCCRunner) {
|
|
|
|
|
if (sCCTimer) {
|
|
|
|
|
if (ReadyToTriggerExpensiveCollectorTimer()) {
|
|
|
|
|
CCRunnerFired(TimeStamp(), nullptr);
|
|
|
|
|
CCTimerFired(nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sICCRunner) {
|
|
|
|
|
ICCRunnerFired(TimeStamp(), nullptr);
|
|
|
|
|
if (sICCTimer) {
|
|
|
|
|
ICCTimerFired(nullptr, nullptr);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -2150,12 +1952,12 @@ nsJSContext::PokeGC(JS::gcreason::Reason aReason,
|
|
|
|
|
sNeedsFullGC = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sGCTimer || sInterSliceGCRunner) {
|
|
|
|
|
if (sGCTimer || sInterSliceGCTimer) {
|
|
|
|
|
// There's already a timer for GC'ing, just return
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sCCRunner) {
|
|
|
|
|
if (sCCTimer) {
|
|
|
|
|
// Make sure CC is called...
|
|
|
|
|
sNeedsFullCC = true;
|
|
|
|
|
// and GC after it.
|
|
|
|
|
@@ -2163,7 +1965,7 @@ nsJSContext::PokeGC(JS::gcreason::Reason aReason,
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sICCRunner) {
|
|
|
|
|
if (sICCTimer) {
|
|
|
|
|
// Make sure GC is called after the current CC completes.
|
|
|
|
|
// No need to set sNeedsFullCC because we are currently running a CC.
|
|
|
|
|
sNeedsGCAfterCC = true;
|
|
|
|
|
@@ -2189,7 +1991,6 @@ nsJSContext::PokeGC(JS::gcreason::Reason aReason,
|
|
|
|
|
: NS_GC_DELAY),
|
|
|
|
|
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
|
|
|
|
|
"GCTimerFired");
|
|
|
|
|
|
|
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2219,19 +2020,24 @@ nsJSContext::PokeShrinkingGC()
|
|
|
|
|
void
|
|
|
|
|
nsJSContext::MaybePokeCC()
|
|
|
|
|
{
|
|
|
|
|
if (sCCRunner || sICCRunner || sShuttingDown || !sHasRunGC) {
|
|
|
|
|
if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
|
|
|
|
|
sCCRunnerFireCount = 0;
|
|
|
|
|
|
|
|
|
|
sCCTimerFireCount = 0;
|
|
|
|
|
CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
|
|
|
|
|
if (!sCCTimer) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// We can kill some objects before running forgetSkippable.
|
|
|
|
|
nsCycleCollector_dispatchDeferredDeletion();
|
|
|
|
|
|
|
|
|
|
sCCRunner =
|
|
|
|
|
CollectorRunner::Create(CCRunnerFired, NS_CC_SKIPPABLE_DELAY,
|
|
|
|
|
kForgetSkippableSliceDuration, true);
|
|
|
|
|
sCCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
|
|
|
|
|
sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
|
|
|
|
|
NS_CC_SKIPPABLE_DELAY,
|
|
|
|
|
nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
|
|
|
|
|
"CCTimerFired");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2255,11 +2061,11 @@ nsJSContext::KillFullGCTimer()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
nsJSContext::KillInterSliceGCRunner()
|
|
|
|
|
nsJSContext::KillInterSliceGCTimer()
|
|
|
|
|
{
|
|
|
|
|
if (sInterSliceGCRunner) {
|
|
|
|
|
sInterSliceGCRunner->Cancel();
|
|
|
|
|
sInterSliceGCRunner = nullptr;
|
|
|
|
|
if (sInterSliceGCTimer) {
|
|
|
|
|
sInterSliceGCTimer->Cancel();
|
|
|
|
|
NS_RELEASE(sInterSliceGCTimer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2275,24 +2081,24 @@ nsJSContext::KillShrinkingGCTimer()
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
void
|
|
|
|
|
nsJSContext::KillCCRunner()
|
|
|
|
|
nsJSContext::KillCCTimer()
|
|
|
|
|
{
|
|
|
|
|
sCCLockedOutTime = 0;
|
|
|
|
|
if (sCCRunner) {
|
|
|
|
|
sCCRunner->Cancel();
|
|
|
|
|
sCCRunner = nullptr;
|
|
|
|
|
if (sCCTimer) {
|
|
|
|
|
sCCTimer->Cancel();
|
|
|
|
|
NS_RELEASE(sCCTimer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//static
|
|
|
|
|
void
|
|
|
|
|
nsJSContext::KillICCRunner()
|
|
|
|
|
nsJSContext::KillICCTimer()
|
|
|
|
|
{
|
|
|
|
|
sCCLockedOutTime = 0;
|
|
|
|
|
|
|
|
|
|
if (sICCRunner) {
|
|
|
|
|
sICCRunner->Cancel();
|
|
|
|
|
sICCRunner = nullptr;
|
|
|
|
|
if (sICCTimer) {
|
|
|
|
|
sICCTimer->Cancel();
|
|
|
|
|
NS_RELEASE(sICCTimer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2364,8 +2170,8 @@ DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescrip
|
|
|
|
|
sCCLockedOut = false;
|
|
|
|
|
sIsCompactingOnUserInactive = false;
|
|
|
|
|
|
|
|
|
|
// May need to kill the inter-slice GC runner
|
|
|
|
|
nsJSContext::KillInterSliceGCRunner();
|
|
|
|
|
// May need to kill the inter-slice GC timer
|
|
|
|
|
nsJSContext::KillInterSliceGCTimer();
|
|
|
|
|
|
|
|
|
|
sCCollectedWaitingForGC = 0;
|
|
|
|
|
sCCollectedZonesWaitingForGC = 0;
|
|
|
|
|
@@ -2406,11 +2212,15 @@ DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescrip
|
|
|
|
|
case JS::GC_SLICE_END:
|
|
|
|
|
|
|
|
|
|
// Schedule another GC slice if the GC has more work to do.
|
|
|
|
|
nsJSContext::KillInterSliceGCRunner();
|
|
|
|
|
nsJSContext::KillInterSliceGCTimer();
|
|
|
|
|
if (!sShuttingDown && !aDesc.isComplete_) {
|
|
|
|
|
sInterSliceGCRunner =
|
|
|
|
|
CollectorRunner::Create(InterSliceGCRunnerFired, NS_INTERSLICE_GC_DELAY,
|
|
|
|
|
sActiveIntersliceGCBudget, false);
|
|
|
|
|
CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
|
|
|
|
|
sInterSliceGCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
|
|
|
|
|
sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired,
|
|
|
|
|
nullptr,
|
|
|
|
|
NS_INTERSLICE_GC_DELAY,
|
|
|
|
|
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
|
|
|
|
|
"InterSliceGCTimerFired");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
|
|
|
|
|
@@ -2464,7 +2274,7 @@ void
|
|
|
|
|
mozilla::dom::StartupJSEnvironment()
|
|
|
|
|
{
|
|
|
|
|
// initialize all our statics, so that we can restart XPCOM
|
|
|
|
|
sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
|
|
|
|
|
sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr;
|
|
|
|
|
sCCLockedOut = false;
|
|
|
|
|
sCCLockedOutTime = 0;
|
|
|
|
|
sLastCCEndTime = TimeStamp();
|
|
|
|
|
@@ -2799,6 +2609,52 @@ nsJSContext::EnsureStatics()
|
|
|
|
|
sIsInitialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
nsJSContext::NotifyDidPaint()
|
|
|
|
|
{
|
|
|
|
|
sDidPaintAfterPreviousICCSlice = true;
|
|
|
|
|
if (sICCTimer) {
|
|
|
|
|
static uint32_t sCount = 0;
|
|
|
|
|
// 16 here is the common value for refresh driver tick frequency.
|
|
|
|
|
static const uint32_t kTicksPerSliceDelay = kICCIntersliceDelay / 16;
|
|
|
|
|
if (++sCount % kTicksPerSliceDelay != 0) {
|
|
|
|
|
// Don't trigger CC slice all the time after paint, but often still.
|
|
|
|
|
// The key point is to trigger it right after paint, especially when
|
|
|
|
|
// we're running RefreshDriver constantly.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sICCTimer->Cancel();
|
|
|
|
|
ICCTimerFired(nullptr, nullptr);
|
|
|
|
|
if (sICCTimer) {
|
|
|
|
|
sICCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
|
|
|
|
|
sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
|
|
|
|
|
kICCIntersliceDelay,
|
|
|
|
|
nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
|
|
|
|
|
"ICCTimerFired");
|
|
|
|
|
}
|
|
|
|
|
} else if (sCCTimer) {
|
|
|
|
|
static uint32_t sCount = 0;
|
|
|
|
|
static const uint32_t kTicksPerForgetSkippableDelay =
|
|
|
|
|
NS_CC_SKIPPABLE_DELAY / 16;
|
|
|
|
|
if (++sCount % kTicksPerForgetSkippableDelay != 0) {
|
|
|
|
|
// The comment above about triggering CC slice applies to forget skippable
|
|
|
|
|
// too.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sCCTimer->Cancel();
|
|
|
|
|
CCTimerFired(nullptr, nullptr);
|
|
|
|
|
if (sCCTimer) {
|
|
|
|
|
sCCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
|
|
|
|
|
sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
|
|
|
|
|
NS_CC_SKIPPABLE_DELAY,
|
|
|
|
|
nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
|
|
|
|
|
"CCTimerFired");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nsScriptNameSpaceManager*
|
|
|
|
|
mozilla::dom::GetNameSpaceManager()
|
|
|
|
|
{
|
|
|
|
|
|