tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }