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