1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
// Copyright 2015 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 "components/scheduler/renderer/throttling_helper.h"
#include "base/logging.h"
#include "components/scheduler/base/real_time_domain.h"
#include "components/scheduler/child/scheduler_tqm_delegate.h"
#include "components/scheduler/renderer/renderer_scheduler_impl.h"
#include "components/scheduler/renderer/throttled_time_domain.h"
#include "components/scheduler/renderer/web_frame_scheduler_impl.h"
#include "third_party/WebKit/public/platform/WebFrameScheduler.h"
namespace scheduler {
ThrottlingHelper::ThrottlingHelper(RendererSchedulerImpl* renderer_scheduler,
const char* tracing_category)
: task_runner_(renderer_scheduler->ControlTaskRunner()),
renderer_scheduler_(renderer_scheduler),
tick_clock_(renderer_scheduler->tick_clock()),
tracing_category_(tracing_category),
time_domain_(new ThrottledTimeDomain(this, tick_clock_)),
weak_factory_(this) {
suspend_timers_when_backgrounded_closure_.Reset(base::Bind(
&ThrottlingHelper::PumpThrottledTasks, weak_factory_.GetWeakPtr()));
forward_immediate_work_closure_ =
base::Bind(&ThrottlingHelper::OnTimeDomainHasImmediateWork,
weak_factory_.GetWeakPtr());
renderer_scheduler_->RegisterTimeDomain(time_domain_.get());
}
ThrottlingHelper::~ThrottlingHelper() {
// It's possible for queues to be still throttled, so we need to tidy up
// before unregistering the time domain.
for (const TaskQueueMap::value_type& map_entry : throttled_queues_) {
TaskQueue* task_queue = map_entry.first;
task_queue->SetTimeDomain(renderer_scheduler_->real_time_domain());
task_queue->SetPumpPolicy(TaskQueue::PumpPolicy::AUTO);
}
renderer_scheduler_->UnregisterTimeDomain(time_domain_.get());
}
void ThrottlingHelper::IncreaseThrottleRefCount(TaskQueue* task_queue) {
DCHECK_NE(task_queue, task_runner_.get());
std::pair<TaskQueueMap::iterator, bool> insert_result =
throttled_queues_.insert(std::make_pair(task_queue, 1));
if (insert_result.second) {
// The insert was succesful so we need to throttle the queue.
task_queue->SetTimeDomain(time_domain_.get());
task_queue->SetPumpPolicy(TaskQueue::PumpPolicy::MANUAL);
if (!task_queue->IsEmpty()) {
if (task_queue->HasPendingImmediateWork()) {
OnTimeDomainHasImmediateWork();
} else {
OnTimeDomainHasDelayedWork();
}
}
} else {
// An entry already existed in the map so we need to increment the refcount.
insert_result.first->second++;
}
}
void ThrottlingHelper::DecreaseThrottleRefCount(TaskQueue* task_queue) {
TaskQueueMap::iterator iter = throttled_queues_.find(task_queue);
if (iter != throttled_queues_.end() && --iter->second == 0) {
// The refcount has become zero, we need to unthrottle the queue.
throttled_queues_.erase(iter);
task_queue->SetTimeDomain(renderer_scheduler_->real_time_domain());
task_queue->SetPumpPolicy(TaskQueue::PumpPolicy::AUTO);
}
}
void ThrottlingHelper::UnregisterTaskQueue(TaskQueue* task_queue) {
throttled_queues_.erase(task_queue);
}
void ThrottlingHelper::OnTimeDomainHasImmediateWork() {
// Forward to the main thread if called from another thread.
if (!task_runner_->RunsTasksOnCurrentThread()) {
task_runner_->PostTask(FROM_HERE, forward_immediate_work_closure_);
return;
}
TRACE_EVENT0(tracing_category_,
"ThrottlingHelper::OnTimeDomainHasImmediateWork");
base::TimeTicks now = tick_clock_->NowTicks();
MaybeSchedulePumpThrottledTasksLocked(FROM_HERE, now, now);
}
void ThrottlingHelper::OnTimeDomainHasDelayedWork() {
TRACE_EVENT0(tracing_category_,
"ThrottlingHelper::OnTimeDomainHasDelayedWork");
base::TimeTicks next_scheduled_delayed_task;
bool has_delayed_task =
time_domain_->NextScheduledRunTime(&next_scheduled_delayed_task);
DCHECK(has_delayed_task);
base::TimeTicks now = tick_clock_->NowTicks();
MaybeSchedulePumpThrottledTasksLocked(FROM_HERE, now,
next_scheduled_delayed_task);
}
void ThrottlingHelper::PumpThrottledTasks() {
TRACE_EVENT0(tracing_category_, "ThrottlingHelper::PumpThrottledTasks");
pending_pump_throttled_tasks_runtime_ = base::TimeTicks();
base::TimeTicks now = tick_clock_->NowTicks();
time_domain_->AdvanceTo(now);
for (const TaskQueueMap::value_type& map_entry : throttled_queues_) {
TaskQueue* task_queue = map_entry.first;
if (task_queue->IsEmpty())
continue;
task_queue->PumpQueue(false);
}
// Make sure NextScheduledRunTime gives us an up-to date result.
time_domain_->ClearExpiredWakeups();
base::TimeTicks next_scheduled_delayed_task;
// Maybe schedule a call to ThrottlingHelper::PumpThrottledTasks if there is
// a pending delayed task. NOTE posting a non-delayed task in the future will
// result in ThrottlingHelper::OnTimeDomainHasImmediateWork being called.
if (time_domain_->NextScheduledRunTime(&next_scheduled_delayed_task)) {
MaybeSchedulePumpThrottledTasksLocked(FROM_HERE, now,
next_scheduled_delayed_task);
}
}
/* static */
base::TimeTicks ThrottlingHelper::ThrottledRunTime(
base::TimeTicks unthrottled_runtime) {
const base::TimeDelta one_second = base::TimeDelta::FromSeconds(1);
return unthrottled_runtime + one_second -
((unthrottled_runtime - base::TimeTicks()) % one_second);
}
void ThrottlingHelper::MaybeSchedulePumpThrottledTasksLocked(
const tracked_objects::Location& from_here,
base::TimeTicks now,
base::TimeTicks unthrottled_runtime) {
base::TimeTicks throttled_runtime =
ThrottledRunTime(std::max(now, unthrottled_runtime));
// If there is a pending call to PumpThrottledTasks and it's sooner than
// |unthrottled_runtime| then return.
if (!pending_pump_throttled_tasks_runtime_.is_null() &&
throttled_runtime >= pending_pump_throttled_tasks_runtime_) {
return;
}
pending_pump_throttled_tasks_runtime_ = throttled_runtime;
suspend_timers_when_backgrounded_closure_.Cancel();
base::TimeDelta delay = pending_pump_throttled_tasks_runtime_ - now;
TRACE_EVENT1(tracing_category_,
"ThrottlingHelper::MaybeSchedulePumpThrottledTasksLocked",
"delay_till_next_pump_ms", delay.InMilliseconds());
task_runner_->PostDelayedTask(
from_here, suspend_timers_when_backgrounded_closure_.callback(), delay);
}
} // namespace scheduler
|