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