Bug 1678416 - gtest for CCGCScheduler r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D95954
This commit is contained in:
Steve Fink 2020-12-16 00:11:06 +00:00
Родитель 499c211405
Коммит 447ae287a1
4 изменённых файлов: 364 добавлений и 26 удалений

Просмотреть файл

@ -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",
]