TestScheduler.cpp (11660B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "gtest/gtest.h" 7 #include "mozilla/TimeStamp.h" 8 #include "mozilla/dom/CCGCScheduler.h" 9 10 // This is a test for mozilla::CCGCScheduler. 11 12 using namespace mozilla; 13 14 MOZ_RUNINIT static TimeDuration kOneSecond = TimeDuration::FromSeconds(1); 15 MOZ_RUNINIT static TimeDuration kTenthSecond = TimeDuration::FromSeconds(0.1); 16 MOZ_RUNINIT static TimeDuration kFrameDuration = 17 TimeDuration::FromSeconds(1.0 / 60.0); 18 19 MOZ_RUNINIT static mozilla::TimeStamp sNow = TimeStamp::Now(); 20 21 static mozilla::TimeStamp AdvanceTime(TimeDuration aDuration) { 22 sNow += aDuration; 23 return sNow; 24 } 25 26 static TimeStamp Now() { return sNow; } 27 28 static uint32_t sSuspected = 0; 29 30 static uint32_t SuspectedCCObjects() { return sSuspected; } 31 static void SetNumSuspected(uint32_t n) { sSuspected = n; } 32 static void SuspectMore(uint32_t n) { sSuspected += n; } 33 34 using CCRunnerState = mozilla::CCGCScheduler::CCRunnerState; 35 36 class TestGC { 37 protected: 38 CCGCScheduler& mScheduler; 39 40 public: 41 explicit TestGC(CCGCScheduler& aScheduler) : mScheduler(aScheduler) {} 42 void Run(int aNumSlices); 43 }; 44 45 void TestGC::Run(int aNumSlices) { 46 // Make the purple buffer nearly empty so it is itself not an adequate reason 47 // for wanting a CC. 48 static_assert(3 < mozilla::kCCPurpleLimit); 49 SetNumSuspected(3); 50 51 // Running the GC should not influence whether a CC is currently seen as 52 // needed. But the first time we run GC, it will be false; later, we will 53 // have run a GC and set it to true. 54 CCReason neededCCAtStartOfGC = 55 mScheduler.IsCCNeeded(Now(), SuspectedCCObjects()); 56 57 mScheduler.NoteGCBegin(JS::GCReason::API); 58 59 for (int slice = 0; slice < aNumSlices; slice++) { 60 EXPECT_TRUE(mScheduler.InIncrementalGC()); 61 TimeStamp idleDeadline = Now() + kTenthSecond; 62 JS::SliceBudget budget = 63 mScheduler.ComputeInterSliceGCBudget(idleDeadline, Now()); 64 TimeDuration budgetDuration = 65 TimeDuration::FromMilliseconds(budget.timeBudget()); 66 EXPECT_NEAR(budgetDuration.ToSeconds(), 0.1, 1.e-6); 67 // Pretend the GC took exactly the budget. 68 AdvanceTime(budgetDuration); 69 70 EXPECT_EQ(mScheduler.IsCCNeeded(Now(), SuspectedCCObjects()), 71 neededCCAtStartOfGC); 72 73 // Mutator runs for 1 second. 74 AdvanceTime(kOneSecond); 75 } 76 77 mScheduler.NoteGCEnd(); 78 mScheduler.SetNeedsFullGC(false); 79 } 80 81 class TestCC { 82 protected: 83 CCGCScheduler& mScheduler; 84 85 public: 86 explicit TestCC(CCGCScheduler& aScheduler) : mScheduler(aScheduler) {} 87 88 void Run(int aNumSlices) { 89 Prepare(); 90 MaybePokeCC(); 91 TimerFires(aNumSlices); 92 EndCycleCollectionCallback(); 93 KillCCRunner(); 94 } 95 96 virtual void Prepare() = 0; 97 virtual void MaybePokeCC(); 98 virtual void TimerFires(int aNumSlices); 99 virtual void RunSlices(int aNumSlices); 100 virtual void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, 101 int aSliceNum, int aNumSlices) = 0; 102 virtual void ForgetSkippable(); 103 virtual void EndCycleCollectionCallback(); 104 virtual void KillCCRunner(); 105 }; 106 107 void TestCC::MaybePokeCC() { 108 // nsJSContext::MaybePokeCC 109 110 // In all tests so far, we will be running this just after a GC. 111 CCReason reason = mScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()); 112 EXPECT_EQ(reason, CCReason::GC_FINISHED); 113 114 mScheduler.InitCCRunnerStateMachine(CCRunnerState::ReducePurple, reason); 115 EXPECT_TRUE(mScheduler.IsEarlyForgetSkippable()); 116 } 117 118 void TestCC::TimerFires(int aNumSlices) { 119 // Series of CCRunner timer fires. 120 CCRunnerStep step; 121 122 while (true) { 123 SuspectMore(1000); 124 TimeStamp idleDeadline = Now() + kOneSecond; 125 step = 126 mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects()); 127 // Should first see a series of ForgetSkippable actions. 128 if (step.mAction != CCRunnerAction::ForgetSkippable || 129 step.mParam.mRemoveChildless != KeepChildless) { 130 break; 131 } 132 EXPECT_EQ(step.mYield, Yield); 133 ForgetSkippable(); 134 } 135 136 while (step.mYield == Continue) { 137 TimeStamp idleDeadline = Now() + kOneSecond; 138 step = 139 mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects()); 140 } 141 EXPECT_EQ(step.mAction, CCRunnerAction::ForgetSkippable); 142 EXPECT_EQ(step.mParam.mRemoveChildless, RemoveChildless); 143 ForgetSkippable(); 144 145 TimeStamp idleDeadline = Now() + kOneSecond; 146 step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects()); 147 EXPECT_EQ(step.mAction, CCRunnerAction::CleanupContentUnbinder); 148 step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects()); 149 EXPECT_EQ(step.mAction, CCRunnerAction::CleanupDeferred); 150 151 mScheduler.NoteCCBegin(); 152 RunSlices(aNumSlices); 153 } 154 155 void TestCC::ForgetSkippable() { 156 // ...ForgetSkippable would happen here... 157 JS::SliceBudget budget = 158 mScheduler.ComputeForgetSkippableBudget(Now(), Now() + kTenthSecond); 159 EXPECT_NEAR(budget.timeBudget(), kTenthSecond.ToMilliseconds(), 1); 160 AdvanceTime(kTenthSecond); 161 mScheduler.NoteForgetSkippableComplete(Now(), SuspectedCCObjects()); 162 } 163 164 void TestCC::RunSlices(int aNumSlices) { 165 TimeStamp ccStartTime = Now(); 166 TimeStamp prevSliceEnd = ccStartTime; 167 for (int ccslice = 0; ccslice < aNumSlices; ccslice++) { 168 RunSlice(ccStartTime, prevSliceEnd, ccslice, aNumSlices); 169 prevSliceEnd = Now(); 170 } 171 172 SetNumSuspected(0); 173 } 174 175 void TestCC::EndCycleCollectionCallback() { 176 // nsJSContext::EndCycleCollectionCallback 177 CycleCollectorResults results; 178 results.mFreedGCed = 10; 179 results.mFreedJSZones = 2; 180 mScheduler.NoteCCEnd(results, Now()); 181 182 // Because > 0 zones were freed. 183 EXPECT_TRUE(mScheduler.NeedsGCAfterCC()); 184 } 185 186 void TestCC::KillCCRunner() { 187 // nsJSContext::KillCCRunner 188 mScheduler.KillCCRunner(); 189 } 190 191 class TestIdleCC : public TestCC { 192 public: 193 explicit TestIdleCC(CCGCScheduler& aScheduler) : TestCC(aScheduler) {} 194 195 void Prepare() override; 196 void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, int aSliceNum, 197 int aNumSlices) override; 198 }; 199 200 void TestIdleCC::Prepare() { EXPECT_TRUE(!mScheduler.InIncrementalGC()); } 201 202 void TestIdleCC::RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, 203 int aSliceNum, int aNumSlices) { 204 CCRunnerStep step; 205 TimeStamp idleDeadline = Now() + kTenthSecond; 206 207 // The scheduler should request a CycleCollect slice. 208 step = mScheduler.AdvanceCCRunner(idleDeadline, Now(), SuspectedCCObjects()); 209 EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect); 210 211 // nsJSContext::RunCycleCollectorSlice 212 213 EXPECT_FALSE(mScheduler.InIncrementalGC()); 214 bool preferShorter; 215 JS::SliceBudget budget = mScheduler.ComputeCCSliceBudget( 216 idleDeadline, aCCStartTime, aPrevSliceEnd, Now(), &preferShorter); 217 // The scheduler will set the budget to our deadline (0.1sec in the future). 218 EXPECT_NEAR(budget.timeBudget(), kTenthSecond.ToMilliseconds(), 1); 219 EXPECT_FALSE(preferShorter); 220 221 AdvanceTime(kTenthSecond); 222 } 223 224 class TestNonIdleCC : public TestCC { 225 public: 226 explicit TestNonIdleCC(CCGCScheduler& aScheduler) : TestCC(aScheduler) {} 227 228 void Prepare() override; 229 void RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, int aSliceNum, 230 int aNumSlices) override; 231 }; 232 233 void TestNonIdleCC::Prepare() { 234 EXPECT_TRUE(!mScheduler.InIncrementalGC()); 235 236 // Advance time by an hour to give time for a user event in the past. 237 AdvanceTime(TimeDuration::FromSeconds(3600)); 238 } 239 240 void TestNonIdleCC::RunSlice(TimeStamp aCCStartTime, TimeStamp aPrevSliceEnd, 241 int aSliceNum, int aNumSlices) { 242 CCRunnerStep step; 243 TimeStamp nullDeadline; 244 245 // The scheduler should tell us to run a slice of cycle collection. 246 step = mScheduler.AdvanceCCRunner(nullDeadline, Now(), SuspectedCCObjects()); 247 EXPECT_EQ(step.mAction, CCRunnerAction::CycleCollect); 248 249 // nsJSContext::RunCycleCollectorSlice 250 251 EXPECT_FALSE(mScheduler.InIncrementalGC()); 252 253 bool preferShorter; 254 JS::SliceBudget budget = mScheduler.ComputeCCSliceBudget( 255 nullDeadline, aCCStartTime, aPrevSliceEnd, Now(), &preferShorter); 256 if (aSliceNum == 0) { 257 // First slice of the CC, so always use the baseBudget which is 258 // kICCSliceBudget (3ms) for a non-idle slice. 259 EXPECT_NEAR(budget.timeBudget(), kICCSliceBudget.ToMilliseconds(), 0.1); 260 } else if (aSliceNum == 1) { 261 // Second slice still uses the baseBudget, since not much time has passed 262 // so none of the lengthening mechanisms have kicked in yet. 263 EXPECT_NEAR(budget.timeBudget(), kICCSliceBudget.ToMilliseconds(), 0.1); 264 } else if (aSliceNum == 2) { 265 // We're not overrunning kMaxICCDuration, so we don't go unlimited. 266 EXPECT_FALSE(budget.isUnlimited()); 267 // This slice is delayed, slice time should be increased. 268 EXPECT_NEAR(budget.timeBudget(), 269 MainThreadIdlePeriod::GetLongIdlePeriod() / 2, 0.1); 270 } else { 271 // We're not overrunning kMaxICCDuration, so we don't go unlimited. 272 EXPECT_FALSE(budget.isUnlimited()); 273 274 // These slices are not delayed, but enough time has passed that the 275 // dominating factor is now the linear ramp up to max slice time at the 276 // halfway point to kMaxICCDuration. 277 EXPECT_TRUE(budget.timeBudget() > kICCSliceBudget.ToMilliseconds()); 278 EXPECT_TRUE(budget.timeBudget() <= 279 MainThreadIdlePeriod::GetLongIdlePeriod()); 280 } 281 EXPECT_TRUE(preferShorter); // Non-idle prefers shorter slices 282 283 AdvanceTime(TimeDuration::FromMilliseconds(budget.timeBudget())); 284 if (aSliceNum == 1) { 285 // Delay the third slice (only). 286 AdvanceTime(kICCIntersliceDelay * 2); 287 } 288 } 289 290 // Do a GC then CC then GC. 291 static bool BasicScenario(CCGCScheduler& aScheduler, TestGC* aTestGC, 292 TestCC* aTestCC) { 293 // Run a 10-slice incremental GC. 294 aTestGC->Run(10); 295 296 // After a GC, the scheduler should decide to do a full CC regardless of the 297 // number of purple buffer entries. 298 SetNumSuspected(3); 299 EXPECT_EQ(aScheduler.IsCCNeeded(Now(), SuspectedCCObjects()), 300 CCReason::GC_FINISHED); 301 302 // Now we should want to CC. 303 EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()), 304 CCReason::GC_FINISHED); 305 306 // Do a 5-slice CC. 307 aTestCC->Run(5); 308 309 // Not enough suspected objects to deserve a CC. 310 EXPECT_EQ(aScheduler.IsCCNeeded(Now(), SuspectedCCObjects()), 311 CCReason::NO_REASON); 312 EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()), 313 CCReason::NO_REASON); 314 SetNumSuspected(10000); 315 316 // We shouldn't want to CC again yet, it's too soon. 317 EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()), 318 CCReason::NO_REASON); 319 AdvanceTime(mozilla::kCCDelay); 320 321 // *Now* it's time for another CC. 322 EXPECT_EQ(aScheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()), 323 CCReason::MANY_SUSPECTED); 324 325 // Run a 3-slice incremental GC. 326 EXPECT_TRUE(!aScheduler.InIncrementalGC()); 327 aTestGC->Run(3); 328 329 return true; 330 } 331 332 MOZ_RUNINIT static CCGCScheduler scheduler; 333 MOZ_RUNINIT static TestGC gc(scheduler); 334 MOZ_RUNINIT static TestIdleCC ccIdle(scheduler); 335 MOZ_RUNINIT static TestNonIdleCC ccNonIdle(scheduler); 336 337 TEST(TestScheduler, Idle) 338 { 339 // Cannot CC until we GC once. 340 EXPECT_EQ(scheduler.ShouldScheduleCC(Now(), SuspectedCCObjects()), 341 CCReason::NO_REASON); 342 343 EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccIdle)); 344 } 345 346 TEST(TestScheduler, NonIdle) 347 { 348 EXPECT_TRUE(BasicScenario(scheduler, &gc, &ccNonIdle)); 349 }