summaryrefslogtreecommitdiffstats
path: root/dom/base/test/gtest/TestScheduler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/test/gtest/TestScheduler.cpp')
-rw-r--r--dom/base/test/gtest/TestScheduler.cpp348
1 files changed, 348 insertions, 0 deletions
diff --git a/dom/base/test/gtest/TestScheduler.cpp b/dom/base/test/gtest/TestScheduler.cpp
new file mode 100644
index 0000000000..5d04926627
--- /dev/null
+++ b/dom/base/test/gtest/TestScheduler.cpp
@@ -0,0 +1,348 @@
+/* -*- 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 AdvanceTime(TimeDuration aDuration) {
+ sNow += aDuration;
+ return sNow;
+}
+
+static TimeStamp Now() { return sNow; }
+
+static uint32_t sSuspected = 0;
+
+static uint32_t SuspectedCCObjects() { return sSuspected; }
+static void SetNumSuspected(uint32_t n) { sSuspected = n; }
+static void SuspectMore(uint32_t n) { sSuspected += n; }
+
+using CCRunnerState = mozilla::CCGCScheduler::CCRunnerState;
+
+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.
+ CCReason neededCCAtStartOfGC =
+ mScheduler.IsCCNeeded(Now(), SuspectedCCObjects());
+
+ mScheduler.NoteGCBegin(JS::GCReason::API);
+
+ for (int slice = 0; slice < aNumSlices; slice++) {
+ EXPECT_TRUE(mScheduler.InIncrementalGC());
+ TimeStamp idleDeadline = Now() + kTenthSecond;
+ js::SliceBudget budget =
+ mScheduler.ComputeInterSliceGCBudget(idleDeadline, Now());
+ TimeDuration budgetDuration =
+ TimeDuration::FromMilliseconds(budget.timeBudget());
+ EXPECT_NEAR(budgetDuration.ToSeconds(), 0.1, 1.e-6);
+ // Pretend the GC took exactly the budget.
+ AdvanceTime(budgetDuration);
+
+ EXPECT_EQ(mScheduler.IsCCNeeded(Now(), SuspectedCCObjects()),
+ 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
+
+ // In all tests so far, we will be running this just after a GC.
+ CCReason reason = mScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects());
+ EXPECT_EQ(reason, CCReason::GC_FINISHED);
+
+ mScheduler.InitCCRunnerStateMachine(CCRunnerState::ReducePurple, reason);
+ 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.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ // Should first see a series of ForgetSkippable actions.
+ if (step.mAction != CCRunnerAction::ForgetSkippable ||
+ step.mParam.mRemoveChildless != KeepChildless) {
+ break;
+ }
+ EXPECT_EQ(step.mYield, Yield);
+ ForgetSkippable();
+ }
+
+ while (step.mYield == Continue) {
+ TimeStamp idleDeadline = Now() + kOneSecond;
+ step =
+ mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ }
+ EXPECT_EQ(step.mAction, CCRunnerAction::ForgetSkippable);
+ EXPECT_EQ(step.mParam.mRemoveChildless, RemoveChildless);
+ ForgetSkippable();
+
+ TimeStamp idleDeadline = Now() + kOneSecond;
+ step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CleanupContentUnbinder);
+ step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CleanupDeferred);
+
+ mScheduler.NoteCCBegin(CCReason::API, Now(), 0, sSuspected, 0);
+ 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(), kTenthSecond.ToMilliseconds(), 1);
+ AdvanceTime(kTenthSecond);
+ mScheduler.NoteForgetSkippableComplete(Now(), suspectedBefore,
+ SuspectedCCObjects());
+}
+
+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.NoteCCEnd(results, Now(), TimeDuration());
+
+ // Because > 0 zones were freed.
+ EXPECT_TRUE(mScheduler.NeedsGCAfterCC());
+}
+
+void TestCC::KillCCRunner() {
+ // nsJSContext::KillCCRunner
+ mScheduler.KillCCRunner();
+}
+
+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.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect);
+
+ // nsJSContext::RunCycleCollectorSlice
+
+ EXPECT_FALSE(mScheduler.InIncrementalGC());
+ bool preferShorter;
+ js::SliceBudget budget = mScheduler.ComputeCCSliceBudget(
+ idleDeadline, aCCStartTime, aPrevSliceEnd, Now(), &preferShorter);
+ // The scheduler will set the budget to our deadline (0.1sec in the future).
+ EXPECT_NEAR(budget.timeBudget(), 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.AdvanceCCRunner(nullDeadline, Now(), SuspectedCCObjects());
+ EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect);
+
+ // nsJSContext::RunCycleCollectorSlice
+
+ EXPECT_FALSE(mScheduler.InIncrementalGC());
+
+ bool preferShorter;
+ js::SliceBudget budget = mScheduler.ComputeCCSliceBudget(
+ nullDeadline, aCCStartTime, aPrevSliceEnd, Now(), &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(), 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(), 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, slice time should be increased.
+ EXPECT_NEAR(budget.timeBudget(),
+ MainThreadIdlePeriod::GetLongIdlePeriod() / 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() > kICCSliceBudget.ToMilliseconds());
+ EXPECT_TRUE(budget.timeBudget() <=
+ MainThreadIdlePeriod::GetLongIdlePeriod());
+ }
+ EXPECT_TRUE(preferShorter); // Non-idle prefers shorter slices
+
+ AdvanceTime(TimeDuration::FromMilliseconds(budget.timeBudget()));
+ 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_EQ(aScheduler.IsCCNeeded(Now(), SuspectedCCObjects()),
+ CCReason::GC_FINISHED);
+
+ // Now we should want to CC.
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::GC_FINISHED);
+
+ // Do a 5-slice CC.
+ aTestCC->Run(5);
+
+ // Not enough suspected objects to deserve a CC.
+ EXPECT_EQ(aScheduler.IsCCNeeded(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+ SetNumSuspected(10000);
+
+ // We shouldn't want to CC again yet, it's too soon.
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+ AdvanceTime(mozilla::kCCDelay);
+
+ // *Now* it's time for another CC.
+ EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::MANY_SUSPECTED);
+
+ // 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_EQ(scheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()),
+ CCReason::NO_REASON);
+
+ EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccIdle));
+}
+
+TEST(TestScheduler, NonIdle)
+{ EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccNonIdle)); }