зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1678416 - gtest for CCGCScheduler r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D95954
This commit is contained in:
Родитель
499c211405
Коммит
447ae287a1
|
@ -13,52 +13,47 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
static const mozilla::TimeDuration kOneMinute =
|
||||
mozilla::TimeDuration::FromSeconds(60.0f);
|
||||
static const TimeDuration kOneMinute = TimeDuration::FromSeconds(60.0f);
|
||||
|
||||
// The amount of time we wait between a request to CC (after GC ran)
|
||||
// and doing the actual CC.
|
||||
static const mozilla::TimeDuration kCCDelay =
|
||||
mozilla::TimeDuration::FromSeconds(6);
|
||||
static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6);
|
||||
|
||||
static const mozilla::TimeDuration kCCSkippableDelay =
|
||||
mozilla::TimeDuration::FromMilliseconds(250);
|
||||
static const TimeDuration kCCSkippableDelay =
|
||||
TimeDuration::FromMilliseconds(250);
|
||||
|
||||
// In case the cycle collector isn't run at all, we don't want forget skippables
|
||||
// to run too often. So limit the forget skippable cycle to start at earliest 2
|
||||
// seconds after the end of the previous cycle.
|
||||
static const mozilla::TimeDuration kTimeBetweenForgetSkippableCycles =
|
||||
mozilla::TimeDuration::FromSeconds(2);
|
||||
static const TimeDuration kTimeBetweenForgetSkippableCycles =
|
||||
TimeDuration::FromSeconds(2);
|
||||
|
||||
// ForgetSkippable is usually fast, so we can use small budgets.
|
||||
// This isn't a real budget but a hint to IdleTaskRunner whether there
|
||||
// is enough time to call ForgetSkippable.
|
||||
static const mozilla::TimeDuration kForgetSkippableSliceDuration =
|
||||
mozilla::TimeDuration::FromMilliseconds(2);
|
||||
static const TimeDuration kForgetSkippableSliceDuration =
|
||||
TimeDuration::FromMilliseconds(2);
|
||||
|
||||
// Maximum amount of time that should elapse between incremental CC slices
|
||||
static const mozilla::TimeDuration kICCIntersliceDelay =
|
||||
mozilla::TimeDuration::FromMilliseconds(64);
|
||||
static const TimeDuration kICCIntersliceDelay =
|
||||
TimeDuration::FromMilliseconds(64);
|
||||
|
||||
// Time budget for an incremental CC slice when using timer to run it.
|
||||
static const mozilla::TimeDuration kICCSliceBudget =
|
||||
mozilla::TimeDuration::FromMilliseconds(3);
|
||||
static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3);
|
||||
// Minimum budget for an incremental CC slice when using idle time to run it.
|
||||
static const mozilla::TimeDuration kIdleICCSliceBudget =
|
||||
mozilla::TimeDuration::FromMilliseconds(2);
|
||||
static const TimeDuration kIdleICCSliceBudget =
|
||||
TimeDuration::FromMilliseconds(2);
|
||||
|
||||
// Maximum total duration for an ICC
|
||||
static const mozilla::TimeDuration kMaxICCDuration =
|
||||
mozilla::TimeDuration::FromSeconds(2);
|
||||
static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2);
|
||||
|
||||
// Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
|
||||
// objects in the purple buffer.
|
||||
static const mozilla::TimeDuration kCCForced = kOneMinute * 2;
|
||||
static const TimeDuration kCCForced = kOneMinute * 2;
|
||||
static const uint32_t kCCForcedPurpleLimit = 10;
|
||||
|
||||
// Don't allow an incremental GC to lock out the CC for too long.
|
||||
static const mozilla::TimeDuration kMaxCCLockedoutTime =
|
||||
mozilla::TimeDuration::FromSeconds(30);
|
||||
static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30);
|
||||
|
||||
// Trigger a CC if the purple buffer exceeds this size when we check it.
|
||||
static const uint32_t kCCPurpleLimit = 200;
|
||||
|
@ -94,18 +89,18 @@ struct CCRunnerStep {
|
|||
};
|
||||
|
||||
class CCGCScheduler {
|
||||
public:
|
||||
// Mockable functions to interface with the code being scheduled.
|
||||
|
||||
// Current time. In real usage, this will just return TimeStamp::Now(), but
|
||||
// tests can reimplement it to return a value controlled by the test.
|
||||
static inline mozilla::TimeStamp Now();
|
||||
static inline TimeStamp Now();
|
||||
|
||||
// Number of entries in the purple buffer (those objects whose ref counts
|
||||
// have been decremented since the previous CC, roughly), and are therefore
|
||||
// "suspected" of being members of cyclic garbage.
|
||||
static inline uint32_t SuspectedCCObjects();
|
||||
|
||||
public:
|
||||
// Parameter setting
|
||||
|
||||
void SetActiveIntersliceGCBudget(TimeDuration aDuration) {
|
||||
|
@ -456,9 +451,9 @@ CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline) {
|
|||
{false, false}, /* CCRunnerState::StartCycleCollection */
|
||||
{false, false}, /* CCRunnerState::CycleCollecting */
|
||||
{false, false}}; /* CCRunnerState::Canceled */
|
||||
static_assert(mozilla::ArrayLength(stateDescriptors) ==
|
||||
size_t(CCRunnerState::NumStates),
|
||||
"need one state descriptor per state");
|
||||
static_assert(
|
||||
ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates),
|
||||
"need one state descriptor per state");
|
||||
const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)];
|
||||
|
||||
// Make sure we initialized the state machine.
|
||||
|
|
|
@ -147,6 +147,7 @@ EXPORTS.mozilla.dom += [
|
|||
"BodyStream.h",
|
||||
"BodyUtil.h",
|
||||
"BorrowedAttrInfo.h",
|
||||
"CCGCScheduler.h",
|
||||
"CharacterData.h",
|
||||
"ChildIterator.h",
|
||||
"ChildProcessMessageManager.h",
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "gtest/gtest.h"
|
||||
#include "mozilla/dom/CCGCScheduler.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
// This is a test for mozilla::CCGCScheduler.
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static TimeDuration kOneSecond = TimeDuration::FromSeconds(1);
|
||||
static TimeDuration kTenthSecond = TimeDuration::FromSeconds(0.1);
|
||||
static TimeDuration kFrameDuration = TimeDuration::FromSeconds(1.0 / 60.0);
|
||||
|
||||
static mozilla::TimeStamp sNow = TimeStamp::Now();
|
||||
|
||||
static mozilla::TimeStamp sStartupTime = sNow;
|
||||
|
||||
inline mozilla::TimeStamp mozilla::CCGCScheduler::Now() { return sNow; }
|
||||
|
||||
static mozilla::TimeStamp AdvanceTime(TimeDuration aDuration) {
|
||||
sNow += aDuration;
|
||||
return sNow;
|
||||
}
|
||||
|
||||
static uint32_t sSuspected = 0;
|
||||
|
||||
inline uint32_t mozilla::CCGCScheduler::SuspectedCCObjects() {
|
||||
return sSuspected;
|
||||
}
|
||||
static void SetNumSuspected(uint32_t n) { sSuspected = n; }
|
||||
static void SuspectMore(uint32_t n) { sSuspected += n; }
|
||||
|
||||
using CCRunnerState = mozilla::CCGCScheduler::CCRunnerState;
|
||||
|
||||
static TimeStamp Now() { return sNow; }
|
||||
|
||||
class TestGC {
|
||||
protected:
|
||||
CCGCScheduler& mScheduler;
|
||||
|
||||
public:
|
||||
explicit TestGC(CCGCScheduler& aScheduler) : mScheduler(aScheduler) {}
|
||||
void Run(int aNumSlices);
|
||||
};
|
||||
|
||||
void TestGC::Run(int aNumSlices) {
|
||||
// Make the purple buffer nearly empty so it is itself not an adequate reason
|
||||
// for wanting a CC.
|
||||
static_assert(3 < mozilla::kCCPurpleLimit);
|
||||
SetNumSuspected(3);
|
||||
|
||||
// Running the GC should not influence whether a CC is currently seen as
|
||||
// needed. But the first time we run GC, it will be false; later, we will
|
||||
// have run a GC and set it to true.
|
||||
bool neededCCAtStartOfGC = mScheduler.IsCCNeeded();
|
||||
|
||||
mScheduler.NoteGCBegin();
|
||||
|
||||
for (int slice = 0; slice < aNumSlices; slice++) {
|
||||
EXPECT_TRUE(mScheduler.InIncrementalGC());
|
||||
TimeStamp idleDeadline = Now() + kTenthSecond;
|
||||
TimeDuration budget =
|
||||
mScheduler.ComputeInterSliceGCBudget(idleDeadline, Now());
|
||||
EXPECT_NEAR(budget.ToSeconds(), 0.1, 1.e-6);
|
||||
// Pretend the GC took exactly the budget.
|
||||
AdvanceTime(budget);
|
||||
|
||||
EXPECT_EQ(mScheduler.IsCCNeeded(), neededCCAtStartOfGC);
|
||||
|
||||
// Mutator runs for 1 second.
|
||||
AdvanceTime(kOneSecond);
|
||||
}
|
||||
|
||||
mScheduler.NoteGCEnd();
|
||||
mScheduler.SetNeedsFullGC(false);
|
||||
}
|
||||
|
||||
class TestCC {
|
||||
protected:
|
||||
CCGCScheduler& mScheduler;
|
||||
|
||||
public:
|
||||
explicit TestCC(CCGCScheduler& aScheduler) : mScheduler(aScheduler) {}
|
||||
|
||||
void Run(int aNumSlices) {
|
||||
Prepare();
|
||||
MaybePokeCC();
|
||||
TimerFires(aNumSlices);
|
||||
EndCycleCollectionCallback();
|
||||
KillCCRunner();
|
||||
}
|
||||
|
||||
virtual void Prepare() = 0;
|
||||
virtual void MaybePokeCC();
|
||||
virtual void TimerFires(int aNumSlices);
|
||||
virtual void RunSlices(int aNumSlices);
|
||||
virtual void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd,
|
||||
int aSliceNum, int aNumSlices) = 0;
|
||||
virtual void ForgetSkippable();
|
||||
virtual void EndCycleCollectionCallback();
|
||||
virtual void KillCCRunner();
|
||||
};
|
||||
|
||||
void TestCC::MaybePokeCC() {
|
||||
// nsJSContext::MaybePokeCC
|
||||
EXPECT_TRUE(mScheduler.ShouldScheduleCC());
|
||||
|
||||
mScheduler.InitCCRunnerStateMachine(CCRunnerState::ReducePurple);
|
||||
EXPECT_TRUE(mScheduler.IsEarlyForgetSkippable());
|
||||
}
|
||||
|
||||
void TestCC::TimerFires(int aNumSlices) {
|
||||
// Series of CCRunner timer fires.
|
||||
CCRunnerStep step;
|
||||
|
||||
while (true) {
|
||||
SuspectMore(1000);
|
||||
TimeStamp idleDeadline = Now() + kOneSecond;
|
||||
step = mScheduler.GetNextCCRunnerAction(idleDeadline);
|
||||
// Should first see a series of ForgetSkippable actions.
|
||||
if (step.mAction != CCRunnerAction::ForgetSkippable ||
|
||||
step.mRemoveChildless != KeepChildless) {
|
||||
break;
|
||||
}
|
||||
EXPECT_EQ(step.mYield, Yield);
|
||||
ForgetSkippable();
|
||||
}
|
||||
|
||||
while (step.mYield == Continue) {
|
||||
TimeStamp idleDeadline = Now() + kOneSecond;
|
||||
step = mScheduler.GetNextCCRunnerAction(idleDeadline);
|
||||
}
|
||||
EXPECT_EQ(step.mAction, CCRunnerAction::ForgetSkippable);
|
||||
EXPECT_EQ(step.mRemoveChildless, RemoveChildless);
|
||||
ForgetSkippable();
|
||||
|
||||
TimeStamp idleDeadline = Now() + kOneSecond;
|
||||
step = mScheduler.GetNextCCRunnerAction(idleDeadline);
|
||||
EXPECT_EQ(step.mAction, CCRunnerAction::CleanupContentUnbinder);
|
||||
step = mScheduler.GetNextCCRunnerAction(idleDeadline);
|
||||
EXPECT_EQ(step.mAction, CCRunnerAction::CleanupDeferred);
|
||||
|
||||
RunSlices(aNumSlices);
|
||||
}
|
||||
|
||||
void TestCC::ForgetSkippable() {
|
||||
uint32_t suspectedBefore = sSuspected;
|
||||
// ...ForgetSkippable would happen here...
|
||||
js::SliceBudget budget =
|
||||
mScheduler.ComputeForgetSkippableBudget(Now(), Now() + kTenthSecond);
|
||||
EXPECT_NEAR(budget.timeBudget.budget, kTenthSecond.ToMilliseconds(), 1);
|
||||
AdvanceTime(kTenthSecond);
|
||||
mScheduler.NoteForgetSkippableComplete(Now(), suspectedBefore);
|
||||
}
|
||||
|
||||
void TestCC::RunSlices(int aNumSlices) {
|
||||
TimeStamp ccStartTime = Now();
|
||||
TimeStamp prevSliceEnd = ccStartTime;
|
||||
for (int ccslice = 0; ccslice < aNumSlices; ccslice++) {
|
||||
RunSlice(ccStartTime, prevSliceEnd, ccslice, aNumSlices);
|
||||
prevSliceEnd = Now();
|
||||
}
|
||||
|
||||
SetNumSuspected(0);
|
||||
}
|
||||
|
||||
void TestCC::EndCycleCollectionCallback() {
|
||||
// nsJSContext::EndCycleCollectionCallback
|
||||
CycleCollectorResults results;
|
||||
results.mFreedGCed = 10;
|
||||
results.mFreedJSZones = 2;
|
||||
mScheduler.NoteCycleCollected(results);
|
||||
|
||||
// Because > 0 zones were freed.
|
||||
EXPECT_TRUE(mScheduler.NeedsGCAfterCC());
|
||||
}
|
||||
|
||||
void TestCC::KillCCRunner() {
|
||||
// nsJSContext::KillCCRunner
|
||||
mScheduler.UnblockCC();
|
||||
mScheduler.DeactivateCCRunner();
|
||||
mScheduler.NoteCCEnd(Now());
|
||||
}
|
||||
|
||||
class TestIdleCC : public TestCC {
|
||||
public:
|
||||
explicit TestIdleCC(CCGCScheduler& aScheduler) : TestCC(aScheduler) {}
|
||||
|
||||
void Prepare() override;
|
||||
void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, int aSliceNum,
|
||||
int aNumSlices) override;
|
||||
};
|
||||
|
||||
void TestIdleCC::Prepare() { EXPECT_TRUE(!mScheduler.InIncrementalGC()); }
|
||||
|
||||
void TestIdleCC::RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd,
|
||||
int aSliceNum, int aNumSlices) {
|
||||
CCRunnerStep step;
|
||||
TimeStamp idleDeadline = Now() + kTenthSecond;
|
||||
|
||||
// The scheduler should request a CycleCollect slice.
|
||||
step = mScheduler.GetNextCCRunnerAction(idleDeadline);
|
||||
EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect);
|
||||
|
||||
// nsJSContext::RunCycleCollectorSlice
|
||||
|
||||
EXPECT_FALSE(mScheduler.InIncrementalGC());
|
||||
bool preferShorter;
|
||||
js::SliceBudget budget = mScheduler.ComputeCCSliceBudget(
|
||||
idleDeadline, aCCStartTime, aPrevSliceEnd, &preferShorter);
|
||||
// The scheduler will set the budget to our deadline (0.1sec in the future).
|
||||
EXPECT_NEAR(budget.timeBudget.budget, kTenthSecond.ToMilliseconds(), 1);
|
||||
EXPECT_FALSE(preferShorter);
|
||||
|
||||
AdvanceTime(kTenthSecond);
|
||||
}
|
||||
|
||||
class TestNonIdleCC : public TestCC {
|
||||
public:
|
||||
explicit TestNonIdleCC(CCGCScheduler& aScheduler) : TestCC(aScheduler) {}
|
||||
|
||||
void Prepare() override;
|
||||
void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, int aSliceNum,
|
||||
int aNumSlices) override;
|
||||
};
|
||||
|
||||
void TestNonIdleCC::Prepare() {
|
||||
EXPECT_TRUE(!mScheduler.InIncrementalGC());
|
||||
|
||||
// Advance time by an hour to give time for a user event in the past.
|
||||
AdvanceTime(TimeDuration::FromSeconds(3600));
|
||||
}
|
||||
|
||||
void TestNonIdleCC::RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd,
|
||||
int aSliceNum, int aNumSlices) {
|
||||
CCRunnerStep step;
|
||||
TimeStamp nullDeadline;
|
||||
|
||||
// The scheduler should tell us to run a slice of cycle collection.
|
||||
step = mScheduler.GetNextCCRunnerAction(nullDeadline);
|
||||
EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect);
|
||||
|
||||
// nsJSContext::RunCycleCollectorSlice
|
||||
|
||||
EXPECT_FALSE(mScheduler.InIncrementalGC());
|
||||
|
||||
bool preferShorter;
|
||||
js::SliceBudget budget = mScheduler.ComputeCCSliceBudget(
|
||||
nullDeadline, aCCStartTime, aPrevSliceEnd, &preferShorter);
|
||||
if (aSliceNum == 0) {
|
||||
// First slice of the CC, so always use the baseBudget which is
|
||||
// kICCSliceBudget (3ms) for a non-idle slice.
|
||||
EXPECT_NEAR(budget.timeBudget.budget, kICCSliceBudget.ToMilliseconds(),
|
||||
0.1);
|
||||
} else if (aSliceNum == 1) {
|
||||
// Second slice still uses the baseBudget, since not much time has passed
|
||||
// so none of the lengthening mechanisms have kicked in yet.
|
||||
EXPECT_NEAR(budget.timeBudget.budget, kICCSliceBudget.ToMilliseconds(),
|
||||
0.1);
|
||||
} else if (aSliceNum == 2) {
|
||||
// We're not overrunning kMaxICCDuration, so we don't go unlimited.
|
||||
EXPECT_FALSE(budget.isUnlimited());
|
||||
// This slice is delayed by twice the allowed amount. Slice time should be
|
||||
// doubled.
|
||||
EXPECT_NEAR(budget.timeBudget.budget, kICCSliceBudget.ToMilliseconds() * 2,
|
||||
0.1);
|
||||
} else {
|
||||
// We're not overrunning kMaxICCDuration, so we don't go unlimited.
|
||||
EXPECT_FALSE(budget.isUnlimited());
|
||||
|
||||
// These slices are not delayed, but enough time has passed that the
|
||||
// dominating factor is now the linear ramp up to max slice time at the
|
||||
// halfway point to kMaxICCDuration.
|
||||
EXPECT_TRUE(budget.timeBudget.budget > kICCSliceBudget.ToMilliseconds());
|
||||
EXPECT_TRUE(budget.timeBudget.budget <=
|
||||
MainThreadIdlePeriod::GetLongIdlePeriod());
|
||||
}
|
||||
EXPECT_TRUE(preferShorter); // Non-idle prefers shorter slices
|
||||
|
||||
AdvanceTime(TimeDuration::FromMilliseconds(budget.timeBudget.budget));
|
||||
if (aSliceNum == 1) {
|
||||
// Delay the third slice (only).
|
||||
AdvanceTime(kICCIntersliceDelay * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Do a GC then CC then GC.
|
||||
static bool BasicScenario(CCGCScheduler& aScheduler, TestGC* aTestGC,
|
||||
TestCC* aTestCC) {
|
||||
// Run a 10-slice incremental GC.
|
||||
aTestGC->Run(10);
|
||||
|
||||
// After a GC, the scheduler should decide to do a full CC regardless of the
|
||||
// number of purple buffer entries.
|
||||
SetNumSuspected(3);
|
||||
EXPECT_TRUE(aScheduler.IsCCNeeded());
|
||||
|
||||
// Now we should want to CC.
|
||||
EXPECT_TRUE(aScheduler.ShouldScheduleCC());
|
||||
|
||||
// Do a 5-slice CC.
|
||||
aTestCC->Run(5);
|
||||
|
||||
// Not enough suspected objects to deserve a CC.
|
||||
EXPECT_FALSE(aScheduler.IsCCNeeded());
|
||||
EXPECT_FALSE(aScheduler.ShouldScheduleCC());
|
||||
SetNumSuspected(10000);
|
||||
|
||||
// We shouldn't want to CC again yet, it's too soon.
|
||||
EXPECT_FALSE(aScheduler.ShouldScheduleCC());
|
||||
AdvanceTime(mozilla::kCCDelay);
|
||||
|
||||
// *Now* it's time for another CC.
|
||||
EXPECT_TRUE(aScheduler.ShouldScheduleCC());
|
||||
|
||||
// Run a 3-slice incremental GC.
|
||||
EXPECT_TRUE(!aScheduler.InIncrementalGC());
|
||||
aTestGC->Run(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static CCGCScheduler scheduler;
|
||||
static TestGC gc(scheduler);
|
||||
static TestIdleCC ccIdle(scheduler);
|
||||
static TestNonIdleCC ccNonIdle(scheduler);
|
||||
|
||||
TEST(TestScheduler, Idle)
|
||||
{
|
||||
// Cannot CC until we GC once.
|
||||
EXPECT_FALSE(scheduler.ShouldScheduleCC());
|
||||
|
||||
EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccIdle));
|
||||
}
|
||||
|
||||
TEST(TestScheduler, NonIdle)
|
||||
{ EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccNonIdle)); }
|
|
@ -9,6 +9,7 @@ UNIFIED_SOURCES += [
|
|||
"TestMimeType.cpp",
|
||||
"TestParser.cpp",
|
||||
"TestPlainTextSerializer.cpp",
|
||||
"TestScheduler.cpp",
|
||||
"TestXPathGenerator.cpp",
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче