1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "partition_alloc/starscan/pcscan_scheduling.h"
6 
7 #include "partition_alloc/partition_alloc_base/time/time.h"
8 #include "partition_alloc/partition_alloc_base/time/time_override.h"
9 #include "partition_alloc/partition_lock.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11 
12 namespace partition_alloc::internal {
13 
14 namespace {
15 constexpr size_t kMB = 1024 * 1024;
16 }  // namespace
17 
TEST(PartitionAllocPCScanSchedulerLimitBackendTest,NoScanBelowMinimumScanningThreshold)18 TEST(PartitionAllocPCScanSchedulerLimitBackendTest,
19      NoScanBelowMinimumScanningThreshold) {
20   PCScanScheduler scheduler;
21   LimitBackend limit_backend(scheduler);
22   scheduler.SetNewSchedulingBackend(limit_backend);
23   constexpr size_t kMinimumScanningThreshold =
24       QuarantineData::kQuarantineSizeMinLimit;
25   EXPECT_FALSE(scheduler.AccountFreed(kMinimumScanningThreshold / 2));
26   EXPECT_FALSE(scheduler.AccountFreed(kMinimumScanningThreshold -
27                                       kMinimumScanningThreshold / 2));
28   EXPECT_TRUE(scheduler.AccountFreed(1));
29 }
30 
TEST(PartitionAllocPCScanSchedulerLimitBackendTest,ScanAtQuarantineSizeFraction)31 TEST(PartitionAllocPCScanSchedulerLimitBackendTest,
32      ScanAtQuarantineSizeFraction) {
33   PCScanScheduler scheduler;
34   LimitBackend limit_backend(scheduler);
35   scheduler.SetNewSchedulingBackend(limit_backend);
36   constexpr size_t kHeapSize = 100 * kMB;
37   constexpr size_t kNoSurvivedBytes = 0;
38   limit_backend.UpdateScheduleAfterScan(kNoSurvivedBytes, base::TimeDelta(),
39                                         kHeapSize);
40   constexpr size_t kExpectedTriggerSize = static_cast<size_t>(
41       static_cast<double>(kHeapSize) * LimitBackend::kQuarantineSizeFraction);
42   EXPECT_FALSE(scheduler.AccountFreed(kExpectedTriggerSize / 2));
43   EXPECT_FALSE(
44       scheduler.AccountFreed(kExpectedTriggerSize - kExpectedTriggerSize / 2));
45   EXPECT_TRUE(scheduler.AccountFreed(1));
46 }
47 
48 class PartitionAllocPCScanMUAwareTaskBasedBackendTest : public ::testing::Test {
49  public:
50   static constexpr size_t kHeapSize = 100 * kMB;
51 
HardLimitSize(size_t heap_size)52   static constexpr size_t HardLimitSize(size_t heap_size) {
53     return static_cast<size_t>(
54                static_cast<double>(heap_size) *
55                MUAwareTaskBasedBackend::kHardLimitQuarantineSizePercent) +
56            1;
57   }
58 
SoftLimitSize(size_t heap_size)59   static constexpr size_t SoftLimitSize(size_t heap_size) {
60     return static_cast<size_t>(
61                static_cast<double>(heap_size) *
62                MUAwareTaskBasedBackend::kSoftLimitQuarantineSizePercent) +
63            1;
64   }
65 
PartitionAllocPCScanMUAwareTaskBasedBackendTest()66   PartitionAllocPCScanMUAwareTaskBasedBackendTest()
67       : backend_(scheduler_, &IncrementDelayedScanScheduledCount) {
68     scheduler_.SetNewSchedulingBackend(backend_);
69     constexpr size_t kNoSurvivedBytes = 0;
70     constexpr base::TimeDelta kZeroTimeForScan;
71     backend_.UpdateScheduleAfterScan(kNoSurvivedBytes, kZeroTimeForScan,
72                                      kHeapSize);
73   }
74 
SetUp()75   void SetUp() override { delayed_scan_scheduled_count_ = 0; }
76 
scheduler()77   PCScanScheduler& scheduler() { return scheduler_; }
backend()78   MUAwareTaskBasedBackend& backend() { return backend_; }
delayed_scan_scheduled_count() const79   size_t delayed_scan_scheduled_count() const {
80     return delayed_scan_scheduled_count_;
81   }
82 
83  private:
IncrementDelayedScanScheduledCount(int64_t delay_in_microseconds)84   static void IncrementDelayedScanScheduledCount(
85       int64_t delay_in_microseconds) {
86     ++delayed_scan_scheduled_count_;
87   }
88 
89   static size_t delayed_scan_scheduled_count_;
90   PCScanScheduler scheduler_;
91   MUAwareTaskBasedBackend backend_;
92 };
93 
94 size_t PartitionAllocPCScanMUAwareTaskBasedBackendTest::
95     delayed_scan_scheduled_count_ = 0;
96 
97 namespace {
98 
99 class ScopedTimeTicksOverride final {
100  public:
ScopedTimeTicksOverride()101   ScopedTimeTicksOverride()
102       : ScopedTimeTicksOverride(InitializeTimeAndReturnTimeTicksNow()) {}
103 
AddTicksToNow(base::TimeDelta ticks)104   void AddTicksToNow(base::TimeDelta ticks) { now_ticks_ += ticks; }
105 
106  private:
Now()107   static base::TimeTicks Now() { return now_ticks_; }
108 
InitializeTimeAndReturnTimeTicksNow()109   static base::TimeTicksNowFunction InitializeTimeAndReturnTimeTicksNow() {
110     now_ticks_ = base::TimeTicks::Now();
111     return &Now;
112   }
113 
ScopedTimeTicksOverride(base::TimeTicksNowFunction time_ticks_function)114   explicit ScopedTimeTicksOverride(
115       base::TimeTicksNowFunction time_ticks_function)
116       : overrides_(nullptr, time_ticks_function, nullptr) {}
117 
118   static base::TimeTicks now_ticks_;
119 
120   base::subtle::ScopedTimeClockOverrides overrides_;
121 };
122 
123 // static
124 base::TimeTicks ScopedTimeTicksOverride::now_ticks_;
125 
126 }  // namespace
127 
TEST_F(PartitionAllocPCScanMUAwareTaskBasedBackendTest,SoftLimitSchedulesScanIfMUNotSatisfied)128 TEST_F(PartitionAllocPCScanMUAwareTaskBasedBackendTest,
129        SoftLimitSchedulesScanIfMUNotSatisfied) {
130   // Stop the time.
131   ScopedTimeTicksOverride now_ticks_override;
132   // Simulate PCScan that processed kHeapSize in 1s. Since time is stopped that
133   // schedule is not reachable.
134   backend().UpdateScheduleAfterScan(0, base::Seconds(1), kHeapSize);
135 
136   EXPECT_EQ(0u, delayed_scan_scheduled_count());
137   EXPECT_FALSE(scheduler().AccountFreed(SoftLimitSize(kHeapSize)));
138   EXPECT_EQ(1u, delayed_scan_scheduled_count());
139 }
140 
TEST_F(PartitionAllocPCScanMUAwareTaskBasedBackendTest,SoftLimitInvokesScanIfMUSatisfied)141 TEST_F(PartitionAllocPCScanMUAwareTaskBasedBackendTest,
142        SoftLimitInvokesScanIfMUSatisfied) {
143   // Stop the time.
144   ScopedTimeTicksOverride now_ticks_override;
145   // Simulate PCScan that processed kHeapSize in 0s. The next scan should thus
146   // happen immediately.
147   backend().UpdateScheduleAfterScan(0, base::Seconds(0), kHeapSize);
148 
149   EXPECT_EQ(0u, delayed_scan_scheduled_count());
150   EXPECT_TRUE(scheduler().AccountFreed(SoftLimitSize(kHeapSize)));
151   EXPECT_EQ(0u, delayed_scan_scheduled_count());
152 }
153 
TEST_F(PartitionAllocPCScanMUAwareTaskBasedBackendTest,HardLimitSchedulesScanImmediately)154 TEST_F(PartitionAllocPCScanMUAwareTaskBasedBackendTest,
155        HardLimitSchedulesScanImmediately) {
156   // Stop the time.
157   ScopedTimeTicksOverride now_ticks_override;
158   // Simulate PCScan that processed kHeapSize in 1s. Since time is stopped that
159   // schedule is not reachable.
160   backend().UpdateScheduleAfterScan(0, base::Seconds(0), kHeapSize);
161 
162   EXPECT_EQ(0u, delayed_scan_scheduled_count());
163   // Triogering the hard limit should immediately require a scan and not
164   // schedule anything.
165   EXPECT_TRUE(scheduler().AccountFreed(HardLimitSize(kHeapSize)));
166   EXPECT_EQ(0u, delayed_scan_scheduled_count());
167 }
168 
169 }  // namespace partition_alloc::internal
170