summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.cc')
-rw-r--r--chromium/third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.cc331
1 files changed, 331 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.cc b/chromium/third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.cc
new file mode 100644
index 00000000000..9db40be1c03
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.cc
@@ -0,0 +1,331 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/scheduler/main_thread/agent_scheduling_strategy.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/check.h"
+#include "base/feature_list.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/synchronization/lock.h"
+#include "third_party/blink/renderer/platform/scheduler/common/features.h"
+#include "third_party/blink/renderer/platform/scheduler/common/pollable_thread_safe_flag.h"
+#include "third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.h"
+#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
+
+namespace blink {
+namespace scheduler {
+namespace {
+
+using ::base::sequence_manager::TaskQueue;
+
+using PrioritisationType =
+ ::blink::scheduler::MainThreadTaskQueue::QueueTraits::PrioritisationType;
+
+// Scheduling strategy that does nothing. This emulates the "current" shipped
+// behavior, and is the default unless overridden. Corresponds to the
+// |kNoOpStrategy| feature.
+class NoOpStrategy final : public AgentSchedulingStrategy {
+ public:
+ NoOpStrategy() = default;
+
+ ShouldUpdatePolicy OnFrameAdded(const FrameSchedulerImpl&) override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+ ShouldUpdatePolicy OnFrameRemoved(const FrameSchedulerImpl&) override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+ ShouldUpdatePolicy OnMainFrameFirstMeaningfulPaint(
+ const FrameSchedulerImpl&) override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+ ShouldUpdatePolicy OnInputEvent() override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+ ShouldUpdatePolicy OnDocumentChangedInMainFrame(
+ const FrameSchedulerImpl&) override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+ ShouldUpdatePolicy OnMainFrameLoad(const FrameSchedulerImpl&) override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+ ShouldUpdatePolicy OnDelayPassed(const FrameSchedulerImpl&) override {
+ VerifyValidSequence();
+ return ShouldUpdatePolicy::kNo;
+ }
+
+ base::Optional<bool> QueueEnabledState(
+ const MainThreadTaskQueue& task_queue) const override {
+ VerifyValidSequence();
+ return base::nullopt;
+ }
+ base::Optional<TaskQueue::QueuePriority> QueuePriority(
+ const MainThreadTaskQueue& task_queue) const override {
+ VerifyValidSequence();
+ return base::nullopt;
+ }
+
+ bool ShouldNotifyOnInputEvent() const override { return false; }
+};
+
+// Strategy that keeps track of main frames reaching a certain signal to make
+// scheduling decisions. The exact behavior will be determined by parameter
+// values.
+class TrackMainFrameSignal final : public AgentSchedulingStrategy {
+ public:
+ TrackMainFrameSignal(Delegate& delegate,
+ PerAgentAffectedQueues affected_queue_types,
+ PerAgentSlowDownMethod method,
+ PerAgentSignal signal,
+ base::TimeDelta delay)
+ : delegate_(delegate),
+ affected_queue_types_(affected_queue_types),
+ method_(method),
+ signal_(signal),
+ delay_(delay),
+ waiting_for_input_(&waiting_for_input_lock_) {
+ DCHECK(signal != PerAgentSignal::kDelayOnly || !delay.is_zero())
+ << "Delay duration can not be zero when using |kDelayOnly|.";
+ }
+
+ ShouldUpdatePolicy OnFrameAdded(
+ const FrameSchedulerImpl& frame_scheduler) override {
+ VerifyValidSequence();
+ return OnNewDocument(frame_scheduler);
+ }
+
+ ShouldUpdatePolicy OnFrameRemoved(
+ const FrameSchedulerImpl& frame_scheduler) override {
+ VerifyValidSequence();
+ if (frame_scheduler.GetFrameType() !=
+ FrameScheduler::FrameType::kMainFrame) {
+ return ShouldUpdatePolicy::kNo;
+ }
+
+ main_frames_.erase(&frame_scheduler);
+ main_frames_waiting_for_signal_.erase(&frame_scheduler);
+ if (main_frames_waiting_for_signal_.IsEmpty())
+ SetWaitingForInput(false);
+
+ // TODO(talp): If the frame wasn't in the set to begin with (e.g.: because
+ // it already hit FMP), or if there are still other frames in the set,
+ // then we may not have to trigger a policy update. (But what about cases
+ // where the current agent just changed from main to non-main?)
+ return ShouldUpdatePolicy::kYes;
+ }
+
+ ShouldUpdatePolicy OnMainFrameFirstMeaningfulPaint(
+ const FrameSchedulerImpl& frame_scheduler) override {
+ VerifyValidSequence();
+ DCHECK(frame_scheduler.GetFrameType() ==
+ FrameScheduler::FrameType::kMainFrame);
+
+ return OnSignal(frame_scheduler, PerAgentSignal::kFirstMeaningfulPaint);
+ }
+
+ ShouldUpdatePolicy OnInputEvent() override {
+ VerifyValidSequence();
+
+ // We only use input as a fail-safe for FMP, other signals are more
+ // reliable.
+ DCHECK_EQ(signal_, PerAgentSignal::kFirstMeaningfulPaint)
+ << "OnInputEvent should only be called for FMP-based strategies.";
+
+ if (main_frames_waiting_for_signal_.IsEmpty())
+ return ShouldUpdatePolicy::kNo;
+
+ // Ideally we would like to only remove the frame the input event is related
+ // to, but we don't currently have that information. One suggestion (by
+ // altimin@) is to attribute it to a widget, and apply it to all frames on
+ // the page the widget is on.
+ main_frames_waiting_for_signal_.clear();
+ SetWaitingForInput(false);
+ return ShouldUpdatePolicy::kYes;
+ }
+
+ ShouldUpdatePolicy OnDocumentChangedInMainFrame(
+ const FrameSchedulerImpl& frame_scheduler) override {
+ VerifyValidSequence();
+ return OnNewDocument(frame_scheduler);
+ }
+
+ ShouldUpdatePolicy OnMainFrameLoad(
+ const FrameSchedulerImpl& frame_scheduler) override {
+ VerifyValidSequence();
+ DCHECK(frame_scheduler.GetFrameType() ==
+ FrameScheduler::FrameType::kMainFrame);
+
+ return OnSignal(frame_scheduler, PerAgentSignal::kOnLoad);
+ }
+
+ ShouldUpdatePolicy OnDelayPassed(
+ const FrameSchedulerImpl& frame_scheduler) override {
+ VerifyValidSequence();
+ return SignalReached(frame_scheduler);
+ }
+
+ base::Optional<bool> QueueEnabledState(
+ const MainThreadTaskQueue& task_queue) const override {
+ VerifyValidSequence();
+
+ if (method_ == PerAgentSlowDownMethod::kDisable &&
+ ShouldAffectQueue(task_queue)) {
+ return false;
+ }
+
+ return base::nullopt;
+ }
+
+ base::Optional<TaskQueue::QueuePriority> QueuePriority(
+ const MainThreadTaskQueue& task_queue) const override {
+ VerifyValidSequence();
+
+ if (method_ == PerAgentSlowDownMethod::kBestEffort &&
+ ShouldAffectQueue(task_queue)) {
+ return TaskQueue::QueuePriority::kBestEffortPriority;
+ }
+
+ return base::nullopt;
+ }
+
+ bool ShouldNotifyOnInputEvent() const override {
+ if (signal_ != PerAgentSignal::kFirstMeaningfulPaint)
+ return false;
+
+ return waiting_for_input_.IsSet();
+ }
+
+ private:
+ ShouldUpdatePolicy OnNewDocument(const FrameSchedulerImpl& frame_scheduler) {
+ // For now we *always* return kYes here. It might be possible to optimize
+ // this, but there are a number of tricky cases that need to be taken into
+ // account here: (i) a non-main frame could have navigated between a main
+ // and a non-main agent, possibly requiring policy update for that frame, or
+ // (ii) main frame navigated to a different agent, potentially changing the
+ // main/non-main classification for both the "previous" and "current" agents
+ // and requiring their policies be updated.
+
+ if (frame_scheduler.GetFrameType() !=
+ FrameScheduler::FrameType::kMainFrame) {
+ return ShouldUpdatePolicy::kYes;
+ }
+
+ if (signal_ == PerAgentSignal::kDelayOnly) {
+ delegate_.OnSetTimer(frame_scheduler, delay_);
+ }
+
+ main_frames_.insert(&frame_scheduler);
+
+ // Only add ordinary page frames to the set of waiting frames, as
+ // non-ordinary ones don't report any signals.
+ if (frame_scheduler.IsOrdinary())
+ main_frames_waiting_for_signal_.insert(&frame_scheduler);
+
+ if (signal_ == PerAgentSignal::kFirstMeaningfulPaint)
+ SetWaitingForInput(true);
+
+ return ShouldUpdatePolicy::kYes;
+ }
+
+ bool ShouldAffectQueue(const MainThreadTaskQueue& task_queue) const {
+ // Queues that don't have a frame scheduler are, by definition, not
+ // associated with a frame (or agent).
+ if (!task_queue.GetFrameScheduler())
+ return false;
+
+ if (affected_queue_types_ == PerAgentAffectedQueues::kTimerQueues &&
+ task_queue.GetPrioritisationType() !=
+ PrioritisationType::kJavaScriptTimer) {
+ return false;
+ }
+
+ // Don't do anything if all main frames have reached the signal.
+ if (main_frames_waiting_for_signal_.IsEmpty())
+ return false;
+
+ // Otherwise, affect the queue only if it doesn't belong to any main agent.
+ base::UnguessableToken agent_cluster_id =
+ task_queue.GetFrameScheduler()->GetAgentClusterId();
+ return std::all_of(main_frames_.begin(), main_frames_.end(),
+ [agent_cluster_id](const FrameSchedulerImpl* frame) {
+ return frame->GetAgentClusterId() != agent_cluster_id;
+ });
+ }
+
+ ShouldUpdatePolicy OnSignal(const FrameSchedulerImpl& frame_scheduler,
+ PerAgentSignal signal) {
+ if (signal != signal_)
+ return ShouldUpdatePolicy::kNo;
+
+ // If there is no delay, then we have reached the awaited signal.
+ if (delay_.is_zero()) {
+ return SignalReached(frame_scheduler);
+ }
+
+ // No need to update policy if we have to wait for a delay.
+ delegate_.OnSetTimer(frame_scheduler, delay_);
+ return ShouldUpdatePolicy::kNo;
+ }
+
+ ShouldUpdatePolicy SignalReached(const FrameSchedulerImpl& frame_scheduler) {
+ main_frames_waiting_for_signal_.erase(&frame_scheduler);
+ if (main_frames_waiting_for_signal_.IsEmpty())
+ SetWaitingForInput(false);
+
+ // TODO(talp): If the frame wasn't in the set to begin with (e.g.: because
+ // an input event cleared it), or if there are still other frames in the
+ // set, then we may not have to trigger a policy update.
+ return ShouldUpdatePolicy::kYes;
+ }
+
+ Delegate& delegate_;
+ const PerAgentAffectedQueues affected_queue_types_;
+ const PerAgentSlowDownMethod method_;
+ const PerAgentSignal signal_;
+ const base::TimeDelta delay_;
+
+ WTF::HashSet<const FrameSchedulerImpl*> main_frames_;
+ WTF::HashSet<const FrameSchedulerImpl*> main_frames_waiting_for_signal_;
+
+ base::Lock waiting_for_input_lock_;
+ PollableThreadSafeFlag waiting_for_input_;
+ void SetWaitingForInput(bool waiting_for_input) {
+ if (waiting_for_input_.IsSet() != waiting_for_input) {
+ base::AutoLock lock(waiting_for_input_lock_);
+ waiting_for_input_.SetWhileLocked(waiting_for_input);
+ }
+ }
+};
+} // namespace
+
+AgentSchedulingStrategy::~AgentSchedulingStrategy() {
+ VerifyValidSequence();
+}
+
+std::unique_ptr<AgentSchedulingStrategy> AgentSchedulingStrategy::Create(
+ Delegate& delegate) {
+ if (!base::FeatureList::IsEnabled(kPerAgentSchedulingExperiments))
+ return std::make_unique<NoOpStrategy>();
+
+ return std::make_unique<TrackMainFrameSignal>(
+ delegate, kPerAgentQueues.Get(), kPerAgentMethod.Get(),
+ kPerAgentSignal.Get(),
+ base::TimeDelta::FromMilliseconds(kPerAgentDelayMs.Get()));
+}
+
+void AgentSchedulingStrategy::VerifyValidSequence() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(main_thread_sequence_checker_);
+}
+
+} // namespace scheduler
+} // namespace blink