1 // Copyright 2020 The Chromium Authors. All rights reserved.
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 "third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.h"
6
7 #include <algorithm>
8 #include <memory>
9
10 #include "base/check.h"
11 #include "base/feature_list.h"
12 #include "base/optional.h"
13 #include "base/sequence_checker.h"
14 #include "base/synchronization/lock.h"
15 #include "third_party/blink/renderer/platform/scheduler/common/features.h"
16 #include "third_party/blink/renderer/platform/scheduler/common/pollable_thread_safe_flag.h"
17 #include "third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.h"
18 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
19
20 namespace blink {
21 namespace scheduler {
22 namespace {
23
24 using ::base::sequence_manager::TaskQueue;
25
26 using PrioritisationType =
27 ::blink::scheduler::MainThreadTaskQueue::QueueTraits::PrioritisationType;
28
29 // Scheduling strategy that does nothing. This emulates the "current" shipped
30 // behavior, and is the default unless overridden. Corresponds to the
31 // |kNoOpStrategy| feature.
32 class NoOpStrategy final : public AgentSchedulingStrategy {
33 public:
34 NoOpStrategy() = default;
35
OnFrameAdded(const FrameSchedulerImpl &)36 ShouldUpdatePolicy OnFrameAdded(const FrameSchedulerImpl&) override {
37 VerifyValidSequence();
38 return ShouldUpdatePolicy::kNo;
39 }
OnFrameRemoved(const FrameSchedulerImpl &)40 ShouldUpdatePolicy OnFrameRemoved(const FrameSchedulerImpl&) override {
41 VerifyValidSequence();
42 return ShouldUpdatePolicy::kNo;
43 }
OnMainFrameFirstMeaningfulPaint(const FrameSchedulerImpl &)44 ShouldUpdatePolicy OnMainFrameFirstMeaningfulPaint(
45 const FrameSchedulerImpl&) override {
46 VerifyValidSequence();
47 return ShouldUpdatePolicy::kNo;
48 }
OnInputEvent()49 ShouldUpdatePolicy OnInputEvent() override {
50 VerifyValidSequence();
51 return ShouldUpdatePolicy::kNo;
52 }
OnDocumentChangedInMainFrame(const FrameSchedulerImpl &)53 ShouldUpdatePolicy OnDocumentChangedInMainFrame(
54 const FrameSchedulerImpl&) override {
55 VerifyValidSequence();
56 return ShouldUpdatePolicy::kNo;
57 }
OnMainFrameLoad(const FrameSchedulerImpl &)58 ShouldUpdatePolicy OnMainFrameLoad(const FrameSchedulerImpl&) override {
59 VerifyValidSequence();
60 return ShouldUpdatePolicy::kNo;
61 }
OnDelayPassed(const FrameSchedulerImpl &)62 ShouldUpdatePolicy OnDelayPassed(const FrameSchedulerImpl&) override {
63 VerifyValidSequence();
64 return ShouldUpdatePolicy::kNo;
65 }
66
QueueEnabledState(const MainThreadTaskQueue & task_queue) const67 base::Optional<bool> QueueEnabledState(
68 const MainThreadTaskQueue& task_queue) const override {
69 VerifyValidSequence();
70 return base::nullopt;
71 }
QueuePriority(const MainThreadTaskQueue & task_queue) const72 base::Optional<TaskQueue::QueuePriority> QueuePriority(
73 const MainThreadTaskQueue& task_queue) const override {
74 VerifyValidSequence();
75 return base::nullopt;
76 }
77
ShouldNotifyOnInputEvent() const78 bool ShouldNotifyOnInputEvent() const override { return false; }
79 };
80
81 // Strategy that keeps track of main frames reaching a certain signal to make
82 // scheduling decisions. The exact behavior will be determined by parameter
83 // values.
84 class TrackMainFrameSignal final : public AgentSchedulingStrategy {
85 public:
TrackMainFrameSignal(Delegate & delegate,PerAgentAffectedQueues affected_queue_types,PerAgentSlowDownMethod method,PerAgentSignal signal,base::TimeDelta delay)86 TrackMainFrameSignal(Delegate& delegate,
87 PerAgentAffectedQueues affected_queue_types,
88 PerAgentSlowDownMethod method,
89 PerAgentSignal signal,
90 base::TimeDelta delay)
91 : delegate_(delegate),
92 affected_queue_types_(affected_queue_types),
93 method_(method),
94 signal_(signal),
95 delay_(delay),
96 waiting_for_input_(&waiting_for_input_lock_) {
97 DCHECK(signal != PerAgentSignal::kDelayOnly || !delay.is_zero())
98 << "Delay duration can not be zero when using |kDelayOnly|.";
99 }
100
OnFrameAdded(const FrameSchedulerImpl & frame_scheduler)101 ShouldUpdatePolicy OnFrameAdded(
102 const FrameSchedulerImpl& frame_scheduler) override {
103 VerifyValidSequence();
104 return OnNewDocument(frame_scheduler);
105 }
106
OnFrameRemoved(const FrameSchedulerImpl & frame_scheduler)107 ShouldUpdatePolicy OnFrameRemoved(
108 const FrameSchedulerImpl& frame_scheduler) override {
109 VerifyValidSequence();
110 if (frame_scheduler.GetFrameType() !=
111 FrameScheduler::FrameType::kMainFrame) {
112 return ShouldUpdatePolicy::kNo;
113 }
114
115 main_frames_.erase(&frame_scheduler);
116 main_frames_waiting_for_signal_.erase(&frame_scheduler);
117 if (main_frames_waiting_for_signal_.IsEmpty())
118 SetWaitingForInput(false);
119
120 // TODO(talp): If the frame wasn't in the set to begin with (e.g.: because
121 // it already hit FMP), or if there are still other frames in the set,
122 // then we may not have to trigger a policy update. (But what about cases
123 // where the current agent just changed from main to non-main?)
124 return ShouldUpdatePolicy::kYes;
125 }
126
OnMainFrameFirstMeaningfulPaint(const FrameSchedulerImpl & frame_scheduler)127 ShouldUpdatePolicy OnMainFrameFirstMeaningfulPaint(
128 const FrameSchedulerImpl& frame_scheduler) override {
129 VerifyValidSequence();
130 DCHECK(frame_scheduler.GetFrameType() ==
131 FrameScheduler::FrameType::kMainFrame);
132
133 return OnSignal(frame_scheduler, PerAgentSignal::kFirstMeaningfulPaint);
134 }
135
OnInputEvent()136 ShouldUpdatePolicy OnInputEvent() override {
137 VerifyValidSequence();
138
139 // We only use input as a fail-safe for FMP, other signals are more
140 // reliable.
141 DCHECK_EQ(signal_, PerAgentSignal::kFirstMeaningfulPaint)
142 << "OnInputEvent should only be called for FMP-based strategies.";
143
144 if (main_frames_waiting_for_signal_.IsEmpty())
145 return ShouldUpdatePolicy::kNo;
146
147 // Ideally we would like to only remove the frame the input event is related
148 // to, but we don't currently have that information. One suggestion (by
149 // altimin@) is to attribute it to a widget, and apply it to all frames on
150 // the page the widget is on.
151 main_frames_waiting_for_signal_.clear();
152 SetWaitingForInput(false);
153 return ShouldUpdatePolicy::kYes;
154 }
155
OnDocumentChangedInMainFrame(const FrameSchedulerImpl & frame_scheduler)156 ShouldUpdatePolicy OnDocumentChangedInMainFrame(
157 const FrameSchedulerImpl& frame_scheduler) override {
158 VerifyValidSequence();
159 return OnNewDocument(frame_scheduler);
160 }
161
OnMainFrameLoad(const FrameSchedulerImpl & frame_scheduler)162 ShouldUpdatePolicy OnMainFrameLoad(
163 const FrameSchedulerImpl& frame_scheduler) override {
164 VerifyValidSequence();
165 DCHECK(frame_scheduler.GetFrameType() ==
166 FrameScheduler::FrameType::kMainFrame);
167
168 return OnSignal(frame_scheduler, PerAgentSignal::kOnLoad);
169 }
170
OnDelayPassed(const FrameSchedulerImpl & frame_scheduler)171 ShouldUpdatePolicy OnDelayPassed(
172 const FrameSchedulerImpl& frame_scheduler) override {
173 VerifyValidSequence();
174 return SignalReached(frame_scheduler);
175 }
176
QueueEnabledState(const MainThreadTaskQueue & task_queue) const177 base::Optional<bool> QueueEnabledState(
178 const MainThreadTaskQueue& task_queue) const override {
179 VerifyValidSequence();
180
181 if (method_ == PerAgentSlowDownMethod::kDisable &&
182 ShouldAffectQueue(task_queue)) {
183 return false;
184 }
185
186 return base::nullopt;
187 }
188
QueuePriority(const MainThreadTaskQueue & task_queue) const189 base::Optional<TaskQueue::QueuePriority> QueuePriority(
190 const MainThreadTaskQueue& task_queue) const override {
191 VerifyValidSequence();
192
193 if (method_ == PerAgentSlowDownMethod::kBestEffort &&
194 ShouldAffectQueue(task_queue)) {
195 return TaskQueue::QueuePriority::kBestEffortPriority;
196 }
197
198 return base::nullopt;
199 }
200
ShouldNotifyOnInputEvent() const201 bool ShouldNotifyOnInputEvent() const override {
202 if (signal_ != PerAgentSignal::kFirstMeaningfulPaint)
203 return false;
204
205 return waiting_for_input_.IsSet();
206 }
207
208 private:
OnNewDocument(const FrameSchedulerImpl & frame_scheduler)209 ShouldUpdatePolicy OnNewDocument(const FrameSchedulerImpl& frame_scheduler) {
210 // For now we *always* return kYes here. It might be possible to optimize
211 // this, but there are a number of tricky cases that need to be taken into
212 // account here: (i) a non-main frame could have navigated between a main
213 // and a non-main agent, possibly requiring policy update for that frame, or
214 // (ii) main frame navigated to a different agent, potentially changing the
215 // main/non-main classification for both the "previous" and "current" agents
216 // and requiring their policies be updated.
217
218 if (frame_scheduler.GetFrameType() !=
219 FrameScheduler::FrameType::kMainFrame) {
220 return ShouldUpdatePolicy::kYes;
221 }
222
223 if (signal_ == PerAgentSignal::kDelayOnly)
224 delegate_.OnSetTimer(frame_scheduler, delay_);
225 else if (signal_ == PerAgentSignal::kFirstMeaningfulPaint)
226 SetWaitingForInput(true);
227
228 main_frames_.insert(&frame_scheduler);
229
230 // Only add ordinary page frames to the set of waiting frames, as
231 // non-ordinary ones don't report any signals.
232 if (frame_scheduler.IsOrdinary())
233 main_frames_waiting_for_signal_.insert(&frame_scheduler);
234
235 return ShouldUpdatePolicy::kYes;
236 }
237
ShouldAffectQueue(const MainThreadTaskQueue & task_queue) const238 bool ShouldAffectQueue(const MainThreadTaskQueue& task_queue) const {
239 // Queues that don't have a frame scheduler are, by definition, not
240 // associated with a frame (or agent).
241 if (!task_queue.GetFrameScheduler())
242 return false;
243
244 if (affected_queue_types_ == PerAgentAffectedQueues::kTimerQueues &&
245 task_queue.GetPrioritisationType() !=
246 PrioritisationType::kJavaScriptTimer) {
247 return false;
248 }
249
250 // Don't do anything if all main frames have reached the signal.
251 if (main_frames_waiting_for_signal_.IsEmpty())
252 return false;
253
254 // Otherwise, affect the queue only if it doesn't belong to any main agent.
255 base::UnguessableToken agent_cluster_id =
256 task_queue.GetFrameScheduler()->GetAgentClusterId();
257 return std::all_of(main_frames_.begin(), main_frames_.end(),
258 [agent_cluster_id](const FrameSchedulerImpl* frame) {
259 return frame->GetAgentClusterId() != agent_cluster_id;
260 });
261 }
262
OnSignal(const FrameSchedulerImpl & frame_scheduler,PerAgentSignal signal)263 ShouldUpdatePolicy OnSignal(const FrameSchedulerImpl& frame_scheduler,
264 PerAgentSignal signal) {
265 if (signal != signal_)
266 return ShouldUpdatePolicy::kNo;
267
268 // If there is no delay, then we have reached the awaited signal.
269 if (delay_.is_zero()) {
270 return SignalReached(frame_scheduler);
271 }
272
273 // No need to update policy if we have to wait for a delay.
274 delegate_.OnSetTimer(frame_scheduler, delay_);
275 return ShouldUpdatePolicy::kNo;
276 }
277
SignalReached(const FrameSchedulerImpl & frame_scheduler)278 ShouldUpdatePolicy SignalReached(const FrameSchedulerImpl& frame_scheduler) {
279 main_frames_waiting_for_signal_.erase(&frame_scheduler);
280 if (main_frames_waiting_for_signal_.IsEmpty())
281 SetWaitingForInput(false);
282
283 // TODO(talp): If the frame wasn't in the set to begin with (e.g.: because
284 // an input event cleared it), or if there are still other frames in the
285 // set, then we may not have to trigger a policy update.
286 return ShouldUpdatePolicy::kYes;
287 }
288
289 Delegate& delegate_;
290 const PerAgentAffectedQueues affected_queue_types_;
291 const PerAgentSlowDownMethod method_;
292 const PerAgentSignal signal_;
293 const base::TimeDelta delay_;
294
295 WTF::HashSet<const FrameSchedulerImpl*> main_frames_;
296 WTF::HashSet<const FrameSchedulerImpl*> main_frames_waiting_for_signal_;
297
298 base::Lock waiting_for_input_lock_;
299 PollableThreadSafeFlag waiting_for_input_;
SetWaitingForInput(bool waiting_for_input)300 void SetWaitingForInput(bool waiting_for_input) {
301 if (waiting_for_input_.IsSet() != waiting_for_input) {
302 base::AutoLock lock(waiting_for_input_lock_);
303 waiting_for_input_.SetWhileLocked(waiting_for_input);
304 }
305 }
306 };
307 } // namespace
308
~AgentSchedulingStrategy()309 AgentSchedulingStrategy::~AgentSchedulingStrategy() {
310 VerifyValidSequence();
311 }
312
Create(Delegate & delegate)313 std::unique_ptr<AgentSchedulingStrategy> AgentSchedulingStrategy::Create(
314 Delegate& delegate) {
315 if (!base::FeatureList::IsEnabled(kPerAgentSchedulingExperiments))
316 return std::make_unique<NoOpStrategy>();
317
318 return std::make_unique<TrackMainFrameSignal>(
319 delegate, kPerAgentQueues.Get(), kPerAgentMethod.Get(),
320 kPerAgentSignal.Get(),
321 base::TimeDelta::FromMilliseconds(kPerAgentDelayMs.Get()));
322 }
323
VerifyValidSequence() const324 void AgentSchedulingStrategy::VerifyValidSequence() const {
325 DCHECK_CALLED_ON_VALID_SEQUENCE(main_thread_sequence_checker_);
326 }
327
328 } // namespace scheduler
329 } // namespace blink
330