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