• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/allocator/partition_allocator/starscan/pcscan_scheduling.h"
6 
7 #include <algorithm>
8 #include <atomic>
9 
10 #include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h"
11 #include "base/allocator/partition_allocator/partition_alloc_base/time/time.h"
12 #include "base/allocator/partition_allocator/partition_alloc_check.h"
13 #include "base/allocator/partition_allocator/partition_alloc_hooks.h"
14 #include "base/allocator/partition_allocator/partition_lock.h"
15 #include "base/allocator/partition_allocator/starscan/logging.h"
16 #include "base/allocator/partition_allocator/starscan/pcscan.h"
17 
18 namespace partition_alloc::internal {
19 
20 // static
21 constexpr size_t QuarantineData::kQuarantineSizeMinLimit;
22 
SetNewSchedulingBackend(PCScanSchedulingBackend & backend)23 void PCScanScheduler::SetNewSchedulingBackend(
24     PCScanSchedulingBackend& backend) {
25   backend_ = &backend;
26 }
27 
DisableScheduling()28 void PCScanSchedulingBackend::DisableScheduling() {
29   scheduling_enabled_.store(false, std::memory_order_relaxed);
30 }
31 
EnableScheduling()32 void PCScanSchedulingBackend::EnableScheduling() {
33   scheduling_enabled_.store(true, std::memory_order_relaxed);
34   // Check if *Scan needs to be run immediately.
35   if (NeedsToImmediatelyScan())
36     PCScan::PerformScan(PCScan::InvocationMode::kNonBlocking);
37 }
38 
ScanStarted()39 size_t PCScanSchedulingBackend::ScanStarted() {
40   auto& data = GetQuarantineData();
41   data.epoch.fetch_add(1, std::memory_order_relaxed);
42   return data.current_size.exchange(0, std::memory_order_relaxed);
43 }
44 
UpdateDelayedSchedule()45 base::TimeDelta PCScanSchedulingBackend::UpdateDelayedSchedule() {
46   return base::TimeDelta();
47 }
48 
49 // static
50 constexpr double LimitBackend::kQuarantineSizeFraction;
51 
LimitReached()52 bool LimitBackend::LimitReached() {
53   return is_scheduling_enabled();
54 }
55 
UpdateScheduleAfterScan(size_t survived_bytes,base::TimeDelta,size_t heap_size)56 void LimitBackend::UpdateScheduleAfterScan(size_t survived_bytes,
57                                            base::TimeDelta,
58                                            size_t heap_size) {
59   scheduler_.AccountFreed(survived_bytes);
60   // |heap_size| includes the current quarantine size, we intentionally leave
61   // some slack till hitting the limit.
62   auto& data = GetQuarantineData();
63   data.size_limit.store(
64       std::max(QuarantineData::kQuarantineSizeMinLimit,
65                static_cast<size_t>(kQuarantineSizeFraction * heap_size)),
66       std::memory_order_relaxed);
67 }
68 
NeedsToImmediatelyScan()69 bool LimitBackend::NeedsToImmediatelyScan() {
70   return false;
71 }
72 
73 // static
74 constexpr double MUAwareTaskBasedBackend::kSoftLimitQuarantineSizePercent;
75 // static
76 constexpr double MUAwareTaskBasedBackend::kHardLimitQuarantineSizePercent;
77 // static
78 constexpr double MUAwareTaskBasedBackend::kTargetMutatorUtilizationPercent;
79 
MUAwareTaskBasedBackend(PCScanScheduler & scheduler,ScheduleDelayedScanFunc schedule_delayed_scan)80 MUAwareTaskBasedBackend::MUAwareTaskBasedBackend(
81     PCScanScheduler& scheduler,
82     ScheduleDelayedScanFunc schedule_delayed_scan)
83     : PCScanSchedulingBackend(scheduler),
84       schedule_delayed_scan_(schedule_delayed_scan) {
85   PA_DCHECK(schedule_delayed_scan_);
86 }
87 
88 MUAwareTaskBasedBackend::~MUAwareTaskBasedBackend() = default;
89 
LimitReached()90 bool MUAwareTaskBasedBackend::LimitReached() {
91   bool should_reschedule = false;
92   base::TimeDelta reschedule_delay;
93   {
94     ScopedGuard guard(scheduler_lock_);
95     // At this point we reached a limit where the schedule generally wants to
96     // trigger a scan.
97     if (hard_limit_) {
98       // The hard limit is not reset, indicating that the scheduler only hit the
99       // soft limit. See inlined comments for the algorithm.
100       auto& data = GetQuarantineData();
101       PA_DCHECK(hard_limit_ >= QuarantineData::kQuarantineSizeMinLimit);
102       // 1. Update the limit to the hard limit which will always immediately
103       // trigger a scan.
104       data.size_limit.store(hard_limit_, std::memory_order_relaxed);
105       hard_limit_ = 0;
106 
107       // 2. Unlikely case: If also above hard limit, start scan right away. This
108       // ignores explicit PCScan disabling.
109       if (PA_UNLIKELY(data.current_size.load(std::memory_order_relaxed) >
110                       data.size_limit.load(std::memory_order_relaxed))) {
111         return true;
112       }
113 
114       // 3. Check if PCScan was explicitly disabled.
115       if (PA_UNLIKELY(!is_scheduling_enabled())) {
116         return false;
117       }
118 
119       // 4. Otherwise, the soft limit would trigger a scan immediately if the
120       // mutator utilization requirement is satisfied.
121       reschedule_delay = earliest_next_scan_time_ - base::TimeTicks::Now();
122       if (reschedule_delay <= base::TimeDelta()) {
123         // May invoke scan immediately.
124         return true;
125       }
126 
127       PA_PCSCAN_VLOG(3) << "Rescheduling scan with delay: "
128                         << reschedule_delay.InMillisecondsF() << " ms";
129       // 5. If the MU requirement is not satisfied, schedule a delayed scan to
130       // the time instance when MU is satisfied.
131       should_reschedule = true;
132     }
133   }
134   // Don't reschedule under the lock as the callback can call free() and
135   // recursively enter the lock.
136   if (should_reschedule) {
137     schedule_delayed_scan_(reschedule_delay.InMicroseconds());
138     return false;
139   }
140   return true;
141 }
142 
ScanStarted()143 size_t MUAwareTaskBasedBackend::ScanStarted() {
144   ScopedGuard guard(scheduler_lock_);
145 
146   return PCScanSchedulingBackend::ScanStarted();
147 }
148 
UpdateScheduleAfterScan(size_t survived_bytes,base::TimeDelta time_spent_in_scan,size_t heap_size)149 void MUAwareTaskBasedBackend::UpdateScheduleAfterScan(
150     size_t survived_bytes,
151     base::TimeDelta time_spent_in_scan,
152     size_t heap_size) {
153   scheduler_.AccountFreed(survived_bytes);
154 
155   ScopedGuard guard(scheduler_lock_);
156 
157   // |heap_size| includes the current quarantine size, we intentionally leave
158   // some slack till hitting the limit.
159   auto& data = GetQuarantineData();
160   data.size_limit.store(
161       std::max(
162           QuarantineData::kQuarantineSizeMinLimit,
163           static_cast<size_t>(kSoftLimitQuarantineSizePercent * heap_size)),
164       std::memory_order_relaxed);
165   hard_limit_ = std::max(
166       QuarantineData::kQuarantineSizeMinLimit,
167       static_cast<size_t>(kHardLimitQuarantineSizePercent * heap_size));
168 
169   // This computes the time window that the scheduler will reserve for the
170   // mutator. Scanning, unless reaching the hard limit, will generally be
171   // delayed until this time has passed.
172   const auto time_required_on_mutator =
173       time_spent_in_scan * kTargetMutatorUtilizationPercent /
174       (1.0 - kTargetMutatorUtilizationPercent);
175   earliest_next_scan_time_ = base::TimeTicks::Now() + time_required_on_mutator;
176 }
177 
NeedsToImmediatelyScan()178 bool MUAwareTaskBasedBackend::NeedsToImmediatelyScan() {
179   bool should_reschedule = false;
180   base::TimeDelta reschedule_delay;
181   {
182     ScopedGuard guard(scheduler_lock_);
183     // If |hard_limit_| was set to zero, the soft limit was reached. Bail out if
184     // it's not.
185     if (hard_limit_)
186       return false;
187 
188     // Check if mutator utilization requiremet is satisfied.
189     reschedule_delay = earliest_next_scan_time_ - base::TimeTicks::Now();
190     if (reschedule_delay <= base::TimeDelta()) {
191       // May invoke scan immediately.
192       return true;
193     }
194 
195     PA_PCSCAN_VLOG(3) << "Rescheduling scan with delay: "
196                       << reschedule_delay.InMillisecondsF() << " ms";
197     // Schedule a delayed scan to the time instance when MU is satisfied.
198     should_reschedule = true;
199   }
200   // Don't reschedule under the lock as the callback can call free() and
201   // recursively enter the lock.
202   if (should_reschedule)
203     schedule_delayed_scan_(reschedule_delay.InMicroseconds());
204   return false;
205 }
206 
UpdateDelayedSchedule()207 base::TimeDelta MUAwareTaskBasedBackend::UpdateDelayedSchedule() {
208   ScopedGuard guard(scheduler_lock_);
209   // TODO(1197479): Adjust schedule to current heap sizing.
210   const auto delay = earliest_next_scan_time_ - base::TimeTicks::Now();
211   PA_PCSCAN_VLOG(3) << "Schedule is off by " << delay.InMillisecondsF() << "ms";
212   return delay >= base::TimeDelta() ? delay : base::TimeDelta();
213 }
214 
215 }  // namespace partition_alloc::internal
216