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 #ifndef PARTITION_ALLOC_STARSCAN_PCSCAN_SCHEDULING_H_
6 #define PARTITION_ALLOC_STARSCAN_PCSCAN_SCHEDULING_H_
7
8 #include <atomic>
9 #include <cstdint>
10
11 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
12 #include "partition_alloc/partition_alloc_base/component_export.h"
13 #include "partition_alloc/partition_alloc_base/thread_annotations.h"
14 #include "partition_alloc/partition_alloc_base/time/time.h"
15 #include "partition_alloc/partition_lock.h"
16
17 namespace partition_alloc::internal {
18
19 class PCScanScheduler;
20
21 struct QuarantineData final {
22 static constexpr size_t kQuarantineSizeMinLimit = 1 * 1024 * 1024;
23
24 inline constexpr QuarantineData();
25
MinimumScanningThresholdReachedfinal26 bool MinimumScanningThresholdReached() const {
27 return current_size.load(std::memory_order_relaxed) >
28 kQuarantineSizeMinLimit;
29 }
30
31 std::atomic<size_t> current_size{0u};
32 std::atomic<size_t> size_limit{kQuarantineSizeMinLimit};
33 std::atomic<size_t> epoch{0u};
34 };
35
36 // No virtual destructor to allow constant initialization of PCScan as
37 // static global which directly embeds LimitBackend as default backend.
38 #if defined(__clang__)
39 #pragma clang diagnostic push
40 #pragma clang diagnostic ignored "-Wnon-virtual-dtor"
41 #endif
PA_COMPONENT_EXPORT(PARTITION_ALLOC)42 class PA_COMPONENT_EXPORT(PARTITION_ALLOC) PCScanSchedulingBackend {
43 #if defined(__clang__)
44 #pragma clang diagnostic pop
45 #endif
46
47 public:
48 inline constexpr explicit PCScanSchedulingBackend(PCScanScheduler&);
49
50 PCScanSchedulingBackend(const PCScanSchedulingBackend&) = delete;
51 PCScanSchedulingBackend& operator=(const PCScanSchedulingBackend&) = delete;
52
53 void DisableScheduling();
54 void EnableScheduling();
55
56 bool is_scheduling_enabled() const {
57 return scheduling_enabled_.load(std::memory_order_relaxed);
58 }
59
60 inline QuarantineData& GetQuarantineData();
61
62 // Invoked when the limit in PCScanScheduler is reached. Returning true
63 // signals the caller to invoke a scan.
64 virtual bool LimitReached() = 0;
65
66 // Invoked on starting a scan. Returns current quarantine size.
67 virtual size_t ScanStarted();
68
69 // Invoked at the end of a scan to compute a new limit.
70 virtual void UpdateScheduleAfterScan(size_t survived_bytes,
71 base::TimeDelta time_spent_in_scan,
72 size_t heap_size) = 0;
73
74 // Invoked by PCScan to ask for a new timeout for a scheduled PCScan task.
75 // Only invoked if scheduler requests a delayed scan at some point.
76 virtual base::TimeDelta UpdateDelayedSchedule();
77
78 protected:
79 inline bool SchedulingDisabled() const;
80
81 virtual bool NeedsToImmediatelyScan() = 0;
82
83 PCScanScheduler& scheduler_;
84 std::atomic<bool> scheduling_enabled_{true};
85 };
86
87 // Scheduling backend that just considers a single hard limit.
PA_COMPONENT_EXPORT(PARTITION_ALLOC)88 class PA_COMPONENT_EXPORT(PARTITION_ALLOC) LimitBackend final
89 : public PCScanSchedulingBackend {
90 public:
91 static constexpr double kQuarantineSizeFraction = 0.1;
92
93 inline constexpr explicit LimitBackend(PCScanScheduler&);
94
95 bool LimitReached() final;
96 void UpdateScheduleAfterScan(size_t, base::TimeDelta, size_t) final;
97
98 private:
99 bool NeedsToImmediatelyScan() final;
100 };
101
102 // Task based backend that is aware of a target mutator utilization that
103 // specifies how much percent of the execution should be reserved for the
104 // mutator. I.e., the MU-aware scheduler ensures that scans are limit and
105 // there is enough time left for the mutator to execute the actual application
106 // workload.
107 //
108 // See constants below for trigger mechanisms.
PA_COMPONENT_EXPORT(PARTITION_ALLOC)109 class PA_COMPONENT_EXPORT(PARTITION_ALLOC) MUAwareTaskBasedBackend final
110 : public PCScanSchedulingBackend {
111 public:
112 using ScheduleDelayedScanFunc = void (*)(int64_t delay_in_microseconds);
113
114 MUAwareTaskBasedBackend(PCScanScheduler&, ScheduleDelayedScanFunc);
115 ~MUAwareTaskBasedBackend();
116
117 bool LimitReached() final;
118 size_t ScanStarted() final;
119 void UpdateScheduleAfterScan(size_t, base::TimeDelta, size_t) final;
120 base::TimeDelta UpdateDelayedSchedule() final;
121
122 private:
123 // Limit triggering the scheduler. If `kTargetMutatorUtilizationPercent` is
124 // satisfied at this point then a scan is triggered immediately.
125 static constexpr double kSoftLimitQuarantineSizePercent = 0.1;
126 // Hard limit at which a scan is triggered in any case. Avoids blowing up the
127 // heap completely.
128 static constexpr double kHardLimitQuarantineSizePercent = 0.5;
129 // Target mutator utilization that is respected when invoking a scan.
130 // Specifies how much percent of walltime should be spent in the mutator.
131 // Inversely, specifies how much walltime (indirectly CPU) is spent on
132 // memory management in scan.
133 static constexpr double kTargetMutatorUtilizationPercent = 0.90;
134
135 bool NeedsToImmediatelyScan() final;
136
137 // Callback to schedule a delayed scan.
138 const ScheduleDelayedScanFunc schedule_delayed_scan_;
139
140 Lock scheduler_lock_;
141 size_t hard_limit_ PA_GUARDED_BY(scheduler_lock_){0};
142 base::TimeTicks earliest_next_scan_time_ PA_GUARDED_BY(scheduler_lock_);
143
144 friend class PartitionAllocPCScanMUAwareTaskBasedBackendTest;
145 };
146
147 // The scheduler that is embedded in the PCSCan frontend which requires a fast
148 // path for freeing objects. The scheduler holds data needed to invoke a
149 // `PCScanSchedulingBackend` upon hitting a limit. The backend implements
150 // the actual scheduling strategy and is in charge of maintaining limits.
PA_COMPONENT_EXPORT(PARTITION_ALLOC)151 class PA_COMPONENT_EXPORT(PARTITION_ALLOC) PCScanScheduler final {
152 public:
153 inline constexpr PCScanScheduler();
154
155 PCScanScheduler(const PCScanScheduler&) = delete;
156 PCScanScheduler& operator=(const PCScanScheduler&) = delete;
157
158 // Account freed `bytes`. Returns true if scan should be triggered
159 // immediately, and false otherwise.
160 PA_ALWAYS_INLINE bool AccountFreed(size_t bytes);
161
162 size_t epoch() const {
163 return quarantine_data_.epoch.load(std::memory_order_relaxed);
164 }
165
166 // Sets a new scheduling backend that should be used by the scanner.
167 void SetNewSchedulingBackend(PCScanSchedulingBackend&);
168
169 PCScanSchedulingBackend& scheduling_backend() { return *backend_; }
170 const PCScanSchedulingBackend& scheduling_backend() const {
171 return *backend_;
172 }
173
174 private:
175 QuarantineData quarantine_data_{};
176 // The default backend used is a simple LimitBackend that just triggers scan
177 // on reaching a hard limit.
178 LimitBackend default_scheduling_backend_{*this};
179 PCScanSchedulingBackend* backend_ = &default_scheduling_backend_;
180
181 friend PCScanSchedulingBackend;
182 };
183
184 // To please Chromium's clang plugin.
185 constexpr PCScanScheduler::PCScanScheduler() = default;
186 constexpr QuarantineData::QuarantineData() = default;
187
PCScanSchedulingBackend(PCScanScheduler & scheduler)188 constexpr PCScanSchedulingBackend::PCScanSchedulingBackend(
189 PCScanScheduler& scheduler)
190 : scheduler_(scheduler) {}
191
GetQuarantineData()192 QuarantineData& PCScanSchedulingBackend::GetQuarantineData() {
193 return scheduler_.quarantine_data_;
194 }
195
LimitBackend(PCScanScheduler & scheduler)196 constexpr LimitBackend::LimitBackend(PCScanScheduler& scheduler)
197 : PCScanSchedulingBackend(scheduler) {}
198
AccountFreed(size_t size)199 PA_ALWAYS_INLINE bool PCScanScheduler::AccountFreed(size_t size) {
200 const size_t size_before =
201 quarantine_data_.current_size.fetch_add(size, std::memory_order_relaxed);
202 return (size_before + size >
203 quarantine_data_.size_limit.load(std::memory_order_relaxed)) &&
204 backend_->LimitReached();
205 }
206
207 } // namespace partition_alloc::internal
208
209 #endif // PARTITION_ALLOC_STARSCAN_PCSCAN_SCHEDULING_H_
210