Commit 7daea78e authored by Francois Doray's avatar Francois Doray Committed by Commit Bot

[blink scheduler] Intensive wake up throttling.

The Blink Scheduler limits wake ups from throttleable TaskQueues
to 1 per second as soon as the page becomes hidden and non-audible.

This CL adds a feature to additionally limit the wake up rate to
1 per minute after 5 minutes in background (both values are
configurable). Local experiments show that this improves battery
life when multiple background tabs are open (see doc linked on the bug).

Potential future improvements:

- Allow N unthrottled wake ups after a page is backgrounded. Increment
  the budget of unthrottled wake ups when a page communicates with the
  user by updating its title/favicon, playing a sound or displaying a
  notification. This could improve support of Calendar or Reminder apps.

- Wait until network is idle before intensively throttling wake ups.
  The intention is not to throttle pages that are loading. The 5 minutes
  grace period may already opt-out most loading pages.

Bug: 1075553
Change-Id: Id46052276bec3384390b002cc5fd2f501e89670a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2171976
Commit-Queue: François Doray <fdoray@chromium.org>
Reviewed-by: default avatarAlexander Timin <altimin@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#774952}
parent ae6c4ed0
......@@ -14,6 +14,7 @@ blink_platform_sources("scheduler") {
"common/cooperative_scheduling_manager.cc",
"common/dummy_schedulers.cc",
"common/event_loop.cc",
"common/features.cc",
"common/features.h",
"common/frame_or_worker_scheduler.cc",
"common/idle_helper.cc",
......
// 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/common/features.h"
namespace blink {
namespace scheduler {
const base::Feature kIntensiveWakeUpThrottling{
"IntensiveWakeUpThrottling", base::FEATURE_DISABLED_BY_DEFAULT};
const base::FeatureParam<int>
kIntensiveWakeUpThrottling_DurationBetweenWakeUpsSeconds{
&kIntensiveWakeUpThrottling, "duration_between_wake_ups_seconds", 60};
const base::FeatureParam<int> kIntensiveWakeUpThrottling_GracePeriodSeconds{
&kIntensiveWakeUpThrottling, "grace_period_seconds", 5 * 60};
} // namespace scheduler
} // namespace blink
......@@ -180,6 +180,29 @@ const base::Feature kPrioritizeCompositingAndLoadingDuringEarlyLoading{
const base::Feature kHighPriorityDatabaseTaskType{
"HighPriorityDatabaseTaskType", base::FEATURE_DISABLED_BY_DEFAULT};
// When enabled, wake ups from throttleable TaskQueues are limited to 1 per
// |kIntensiveWakeUpThrottling_DurationBetweenWakeUpsSeconds| in a page that has
// been backgrounded for |kIntensiveWakeUpThrottling_GracePeriodSeconds|
// seconds.
//
// Intensive wake up throttling is enforced in addition to other throttling
// mechanisms:
// - 1 wake up per second in a background page or hidden cross-origin frame
// - 1% CPU time in a page that has been backgrounded for 10 seconds
//
// Feature tracking bug: https://crbug.com/1075553
PLATFORM_EXPORT extern const base::Feature kIntensiveWakeUpThrottling;
// Duration between wake ups in seconds. For the kIntensiveWakeUpThrottling
// feature.
PLATFORM_EXPORT extern const base::FeatureParam<int>
kIntensiveWakeUpThrottling_DurationBetweenWakeUpsSeconds;
// Grace period after backgrounding a page during which there is no intensive
// wake up throttling, in seconds. For the kIntensiveWakeUpThrottling feature.
PLATFORM_EXPORT extern const base::FeatureParam<int>
kIntensiveWakeUpThrottling_GracePeriodSeconds;
} // namespace scheduler
} // namespace blink
......
......@@ -163,12 +163,23 @@ class FrameSchedulerImplTest : public testing::Test {
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {}
// Constructs a FrameSchedulerImplTest with a list of features to enable and a
// list of features to disable.
FrameSchedulerImplTest(std::vector<base::Feature> features_to_enable,
std::vector<base::Feature> features_to_disable)
: FrameSchedulerImplTest() {
feature_list_.InitWithFeatures(features_to_enable, features_to_disable);
}
// Constructs a FrameSchedulerImplTest with a list of features to enable and
// associated params.
explicit FrameSchedulerImplTest(
const std::vector<base::test::ScopedFeatureList::FeatureAndParams>&
features_to_enable)
: FrameSchedulerImplTest() {
feature_list_.InitWithFeaturesAndParameters(features_to_enable, {});
}
~FrameSchedulerImplTest() override = default;
void SetUp() override {
......@@ -441,6 +452,18 @@ void RunTaskOfLength(base::test::TaskEnvironment* task_environment,
task_environment->FastForwardBy(length);
}
class FrameSchedulerImplTestWithIntensiveWakeUpThrottling
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplTestWithIntensiveWakeUpThrottling()
: FrameSchedulerImplTest(
{{kIntensiveWakeUpThrottling,
{// Make the grace period for intensive throttling shorter than
// the grace period for freezing, because freezing hides the
// effect of intensive throttling.
{kIntensiveWakeUpThrottling_GracePeriodSeconds.name, "60"}}}}) {}
};
} // namespace
// Throttleable task queue is initialized lazily, so there're two scenarios:
......@@ -2403,6 +2426,82 @@ TEST_F(FrameSchedulerImplTest, ThrottledJSTimerTasksRunTime) {
start + base::TimeDelta::FromMilliseconds(6000)));
}
TEST_F(FrameSchedulerImplTestWithIntensiveWakeUpThrottling, TaskExecution) {
constexpr int kNumTasks = 5;
constexpr base::TimeDelta kShortDelay = base::TimeDelta::FromSeconds(1);
const base::TimeDelta kGracePeriod = base::TimeDelta::FromSeconds(
kIntensiveWakeUpThrottling_GracePeriodSeconds.Get());
const base::TimeDelta kDurationBetweenWakeUps = base::TimeDelta::FromSeconds(
kIntensiveWakeUpThrottling_DurationBetweenWakeUpsSeconds.Get());
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(TaskType::kJavascriptTimer);
// Snap the time to a multiple of |kDurationBetweenWakeUps|. Otherwise,
// the time at which tasks can run after throttling is enabled will vary.
const base::TimeTicks start_time = base::TimeTicks::Now();
const base::TimeTicks aligned_start_time =
start_time.SnappedToNextTick(base::TimeTicks(), kDurationBetweenWakeUps);
if (aligned_start_time != start_time)
task_environment_.FastForwardBy(aligned_start_time - start_time);
// Hide the page. This starts the delay to throttle background wake ups.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
// Initially, wake ups are not throttled.
{
int counter = 0;
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter, base::Unretained(&counter)),
i * kShortDelay);
}
EXPECT_EQ(0, counter);
task_environment_.FastForwardBy(kShortDelay * kNumTasks);
EXPECT_EQ(kNumTasks, counter);
task_environment_.FastForwardBy(kGracePeriod - kNumTasks * kShortDelay);
}
// After |kGracePeriod|, wake ups are limited to 1 per
// |kDurationBetweenWakeUps|.
{
int counter = 0;
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter, base::Unretained(&counter)),
(i + 1) * kShortDelay);
}
EXPECT_EQ(0, counter);
// No task should run before |kDurationBetweenWakeUps| has elapsed.
for (int i = 0; i < kNumTasks; ++i) {
task_environment_.FastForwardBy(kShortDelay);
EXPECT_EQ(0, counter);
}
// All tasks should run after |kDurationBetweenWakeUps| has elapsed.
task_environment_.FastForwardBy(kDurationBetweenWakeUps -
kNumTasks * kShortDelay);
EXPECT_EQ(kNumTasks, counter);
}
{
// Post an extra task. It should run after another |kDurationBetweenWakeUps|
// has elapsed.
int counter = 0;
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter, base::Unretained(&counter)),
kShortDelay);
task_environment_.FastForwardBy(kShortDelay);
EXPECT_EQ(0, counter);
task_environment_.FastForwardBy(kDurationBetweenWakeUps - kShortDelay);
EXPECT_EQ(1, counter);
}
}
} // namespace frame_scheduler_impl_unittest
} // namespace scheduler
} // namespace blink
......@@ -15,6 +15,7 @@
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/common/features.h"
#include "third_party/blink/renderer/platform/scheduler/common/throttling/budget_pool.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/auto_advancing_virtual_time_domain.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h"
......@@ -59,8 +60,8 @@ constexpr base::TimeDelta kDefaultDelayForBackgroundTabFreezing =
constexpr base::TimeDelta kDefaultDelayForBackgroundAndNetworkIdleTabFreezing =
base::TimeDelta::FromMinutes(1);
// Interval between throttled wake ups.
constexpr base::TimeDelta kThrottledWakeUpInterval =
// Interval between throttled wake ups, when intensive throttling is disabled.
constexpr base::TimeDelta kDefaultThrottledWakeUpInterval =
base::TimeDelta::FromSeconds(1);
// Duration of a throttled wake up.
......@@ -160,6 +161,7 @@ PageSchedulerImpl::PageSchedulerImpl(
nested_runloop_(false),
is_main_frame_local_(false),
is_cpu_time_throttled_(false),
are_wake_ups_intensively_throttled_(false),
keep_active_(main_thread_scheduler->SchedulerKeepActive()),
cpu_time_budget_pool_(nullptr),
wake_up_budget_pool_(nullptr),
......@@ -173,8 +175,11 @@ PageSchedulerImpl::PageSchedulerImpl(
this, kDefaultPageVisibility == PageVisibilityState::kVisible
? PageLifecycleState::kActive
: PageLifecycleState::kHiddenBackgrounded));
do_throttle_page_callback_.Reset(base::BindRepeating(
&PageSchedulerImpl::DoThrottlePage, base::Unretained(this)));
do_throttle_cpu_time_callback_.Reset(base::BindRepeating(
&PageSchedulerImpl::DoThrottleCPUTime, base::Unretained(this)));
do_intensively_throttle_wake_ups_callback_.Reset(
base::BindRepeating(&PageSchedulerImpl::DoIntensivelyThrottleWakeUps,
base::Unretained(this)));
on_audio_silent_closure_.Reset(base::BindRepeating(
&PageSchedulerImpl::OnAudioSilent, base::Unretained(this)));
do_freeze_page_callback_.Reset(base::BindRepeating(
......@@ -496,7 +501,10 @@ void PageSchedulerImpl::OnAggressiveThrottlingStatusUpdated() {
opted_out_from_aggressive_throttling) {
opted_out_from_aggressive_throttling_ =
opted_out_from_aggressive_throttling;
UpdateBackgroundBudgetPoolSchedulingLifecycleState();
base::sequence_manager::LazyNow lazy_now(
main_thread_scheduler_->tick_clock());
UpdateCPUTimeBudgetPool(&lazy_now);
UpdateWakeUpBudgetPool(&lazy_now);
}
}
......@@ -575,7 +583,7 @@ void PageSchedulerImpl::MaybeInitializeBackgroundCPUTimeBudgetPool(
lazy_now->Now(), settings.initial_budget.value());
}
UpdateBackgroundBudgetPoolSchedulingLifecycleState();
UpdateCPUTimeBudgetPool(lazy_now);
}
WakeUpBudgetPool* PageSchedulerImpl::wake_up_budget_pool() {
......@@ -591,9 +599,11 @@ void PageSchedulerImpl::MaybeInitializeWakeUpBudgetPool(
main_thread_scheduler_->task_queue_throttler()->CreateWakeUpBudgetPool(
"Page Wake Up Throttling");
wake_up_budget_pool_->SetWakeUpInterval(lazy_now->Now(),
kThrottledWakeUpInterval);
// The Wake Up Interval is set in UpdateWakeUpBudgetPool(), based on current
// state. The Wake Up Duration is constant and is set here.
wake_up_budget_pool_->SetWakeUpDuration(kThrottledWakeUpDuration);
UpdateWakeUpBudgetPool(lazy_now);
}
void PageSchedulerImpl::OnThrottlingReported(
......@@ -618,37 +628,80 @@ void PageSchedulerImpl::OnThrottlingReported(
void PageSchedulerImpl::UpdateBackgroundSchedulingLifecycleState(
NotificationPolicy notification_policy) {
base::sequence_manager::LazyNow lazy_now(
main_thread_scheduler_->tick_clock());
if (page_visibility_ == PageVisibilityState::kVisible) {
is_cpu_time_throttled_ = false;
do_throttle_page_callback_.Cancel();
UpdateBackgroundBudgetPoolSchedulingLifecycleState();
do_throttle_cpu_time_callback_.Cancel();
UpdateCPUTimeBudgetPool(&lazy_now);
are_wake_ups_intensively_throttled_ = false;
do_intensively_throttle_wake_ups_callback_.Cancel();
UpdateWakeUpBudgetPool(&lazy_now);
} else {
if (cpu_time_budget_pool_) {
main_thread_scheduler_->ControlTaskRunner()->PostDelayedTask(
FROM_HERE, do_throttle_page_callback_.GetCallback(),
FROM_HERE, do_throttle_cpu_time_callback_.GetCallback(),
kThrottlingDelayAfterBackgrounding);
}
if (wake_up_budget_pool_) {
main_thread_scheduler_->ControlTaskRunner()->PostDelayedTask(
FROM_HERE, do_intensively_throttle_wake_ups_callback_.GetCallback(),
base::TimeDelta::FromSeconds(
kIntensiveWakeUpThrottling_GracePeriodSeconds.Get()));
}
}
if (notification_policy == NotificationPolicy::kNotifyFrames)
NotifyFrames();
}
void PageSchedulerImpl::DoThrottlePage() {
do_throttle_page_callback_.Cancel();
void PageSchedulerImpl::DoThrottleCPUTime() {
do_throttle_cpu_time_callback_.Cancel();
is_cpu_time_throttled_ = true;
UpdateBackgroundBudgetPoolSchedulingLifecycleState();
base::sequence_manager::LazyNow lazy_now(
main_thread_scheduler_->tick_clock());
UpdateCPUTimeBudgetPool(&lazy_now);
NotifyFrames();
}
void PageSchedulerImpl::UpdateBackgroundBudgetPoolSchedulingLifecycleState() {
if (!cpu_time_budget_pool_)
return;
void PageSchedulerImpl::DoIntensivelyThrottleWakeUps() {
do_intensively_throttle_wake_ups_callback_.Cancel();
are_wake_ups_intensively_throttled_ = true;
base::sequence_manager::LazyNow lazy_now(
main_thread_scheduler_->tick_clock());
UpdateWakeUpBudgetPool(&lazy_now);
NotifyFrames();
}
void PageSchedulerImpl::UpdateCPUTimeBudgetPool(
base::sequence_manager::LazyNow* lazy_now) {
if (!cpu_time_budget_pool_)
return;
if (is_cpu_time_throttled_ && !opted_out_from_aggressive_throttling_) {
cpu_time_budget_pool_->EnableThrottling(&lazy_now);
cpu_time_budget_pool_->EnableThrottling(lazy_now);
} else {
cpu_time_budget_pool_->DisableThrottling(&lazy_now);
cpu_time_budget_pool_->DisableThrottling(lazy_now);
}
}
void PageSchedulerImpl::UpdateWakeUpBudgetPool(
base::sequence_manager::LazyNow* lazy_now) {
if (!wake_up_budget_pool_)
return;
if (are_wake_ups_intensively_throttled_ &&
!opted_out_from_aggressive_throttling_) {
wake_up_budget_pool_->SetWakeUpInterval(
lazy_now->Now(),
base::TimeDelta::FromSeconds(
kIntensiveWakeUpThrottling_DurationBetweenWakeUpsSeconds.Get()));
} else {
wake_up_budget_pool_->SetWakeUpInterval(lazy_now->Now(),
kDefaultThrottledWakeUpInterval);
}
}
......
......@@ -218,19 +218,19 @@ class PLATFORM_EXPORT PageSchedulerImpl : public PageScheduler {
void UpdateBackgroundSchedulingLifecycleState(
NotificationPolicy notification_policy);
// As a part of UpdateBackgroundSchedulingLifecycleState set correct
// background_time_budget_pool_ state depending on page visibility and
// number of active connections.
void UpdateBackgroundBudgetPoolSchedulingLifecycleState();
// Adjusts settings of a budget pool depending on current state of the page.
void UpdateCPUTimeBudgetPool(base::sequence_manager::LazyNow* lazy_now);
void UpdateWakeUpBudgetPool(base::sequence_manager::LazyNow* lazy_now);
// Callback for marking page is silent after a delay since last audible
// signal.
void OnAudioSilent();
// Callback for enabling throttling in background after specified delay.
// Callbacks for adjusting the settings of a budget pool after a delay.
// TODO(altimin): Trigger throttling depending on the loading state
// of the page.
void DoThrottlePage();
void DoThrottleCPUTime();
void DoIntensivelyThrottleWakeUps();
// Notify frames that the page scheduler state has been updated.
void NotifyFrames();
......@@ -262,11 +262,13 @@ class PLATFORM_EXPORT PageSchedulerImpl : public PageScheduler {
bool nested_runloop_;
bool is_main_frame_local_;
bool is_cpu_time_throttled_;
bool are_wake_ups_intensively_throttled_;
bool keep_active_;
CPUTimeBudgetPool* cpu_time_budget_pool_;
WakeUpBudgetPool* wake_up_budget_pool_;
PageScheduler::Delegate* delegate_;
CancelableClosureHolder do_throttle_page_callback_;
CancelableClosureHolder do_throttle_cpu_time_callback_;
CancelableClosureHolder do_intensively_throttle_wake_ups_callback_;
CancelableClosureHolder on_audio_silent_closure_;
CancelableClosureHolder do_freeze_page_callback_;
const base::TimeDelta delay_for_background_tab_freezing_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment