1 // Copyright 2015 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.
5 #include "content/child/scheduler/scheduler_helper.h"
7 #include "base/trace_event/trace_event.h"
8 #include "base/trace_event/trace_event_argument.h"
9 #include "content/child/scheduler/nestable_single_thread_task_runner.h"
10 #include "content/child/scheduler/prioritizing_task_queue_selector.h"
14 SchedulerHelper::SchedulerHelper(
15 scoped_refptr
<NestableSingleThreadTaskRunner
> main_task_runner
,
16 SchedulerHelperDelegate
* scheduler_helper_delegate
,
17 const char* tracing_category
,
18 const char* disabled_by_default_tracing_category
,
19 size_t total_task_queue_count
)
20 : task_queue_selector_(new PrioritizingTaskQueueSelector()),
22 new TaskQueueManager(total_task_queue_count
,
24 task_queue_selector_
.get(),
25 disabled_by_default_tracing_category
)),
26 idle_period_state_(IdlePeriodState::NOT_IN_IDLE_PERIOD
),
27 scheduler_helper_delegate_(scheduler_helper_delegate
),
29 task_queue_manager_
->TaskRunnerForQueue(QueueId::CONTROL_TASK_QUEUE
)),
30 control_task_after_wakeup_runner_(task_queue_manager_
->TaskRunnerForQueue(
31 QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
)),
33 task_queue_manager_
->TaskRunnerForQueue(QueueId::DEFAULT_TASK_QUEUE
)),
34 tracing_category_(tracing_category
),
35 disabled_by_default_tracing_category_(
36 disabled_by_default_tracing_category
),
38 DCHECK_GE(total_task_queue_count
,
39 static_cast<size_t>(QueueId::TASK_QUEUE_COUNT
));
40 weak_scheduler_ptr_
= weak_factory_
.GetWeakPtr();
41 end_idle_period_closure_
.Reset(
42 base::Bind(&SchedulerHelper::EndIdlePeriod
, weak_scheduler_ptr_
));
43 initiate_next_long_idle_period_closure_
.Reset(base::Bind(
44 &SchedulerHelper::InitiateLongIdlePeriod
, weak_scheduler_ptr_
));
45 initiate_next_long_idle_period_after_wakeup_closure_
.Reset(
46 base::Bind(&SchedulerHelper::InitiateLongIdlePeriodAfterWakeup
,
47 weak_scheduler_ptr_
));
49 idle_task_runner_
= make_scoped_refptr(new SingleThreadIdleTaskRunner(
50 task_queue_manager_
->TaskRunnerForQueue(QueueId::IDLE_TASK_QUEUE
),
51 control_task_after_wakeup_runner_
,
52 base::Bind(&SchedulerHelper::CurrentIdleTaskDeadlineCallback
,
56 task_queue_selector_
->SetQueuePriority(
57 QueueId::CONTROL_TASK_QUEUE
,
58 PrioritizingTaskQueueSelector::CONTROL_PRIORITY
);
60 task_queue_selector_
->SetQueuePriority(
61 QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
,
62 PrioritizingTaskQueueSelector::CONTROL_PRIORITY
);
63 task_queue_manager_
->SetPumpPolicy(
64 QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
,
65 TaskQueueManager::PumpPolicy::AFTER_WAKEUP
);
67 task_queue_selector_
->DisableQueue(QueueId::IDLE_TASK_QUEUE
);
68 task_queue_manager_
->SetPumpPolicy(QueueId::IDLE_TASK_QUEUE
,
69 TaskQueueManager::PumpPolicy::MANUAL
);
71 for (size_t i
= 0; i
< TASK_QUEUE_COUNT
; i
++) {
72 task_queue_manager_
->SetQueueName(
73 i
, TaskQueueIdToString(static_cast<QueueId
>(i
)));
76 // TODO(skyostil): Increase this to 4 (crbug.com/444764).
77 task_queue_manager_
->SetWorkBatchSize(1);
80 SchedulerHelper::~SchedulerHelper() {
83 SchedulerHelper::SchedulerHelperDelegate::SchedulerHelperDelegate() {
86 SchedulerHelper::SchedulerHelperDelegate::~SchedulerHelperDelegate() {
89 void SchedulerHelper::Shutdown() {
91 task_queue_manager_
.reset();
94 scoped_refptr
<base::SingleThreadTaskRunner
>
95 SchedulerHelper::DefaultTaskRunner() {
97 return default_task_runner_
;
100 scoped_refptr
<SingleThreadIdleTaskRunner
> SchedulerHelper::IdleTaskRunner() {
101 CheckOnValidThread();
102 return idle_task_runner_
;
105 scoped_refptr
<base::SingleThreadTaskRunner
>
106 SchedulerHelper::ControlTaskRunner() {
107 return control_task_runner_
;
110 void SchedulerHelper::CurrentIdleTaskDeadlineCallback(
111 base::TimeTicks
* deadline_out
) const {
112 CheckOnValidThread();
113 *deadline_out
= idle_period_deadline_
;
116 SchedulerHelper::IdlePeriodState
SchedulerHelper::ComputeNewLongIdlePeriodState(
117 const base::TimeTicks now
,
118 base::TimeDelta
* next_long_idle_period_delay_out
) {
119 CheckOnValidThread();
121 if (!scheduler_helper_delegate_
->CanEnterLongIdlePeriod(
122 now
, next_long_idle_period_delay_out
)) {
123 return IdlePeriodState::NOT_IN_IDLE_PERIOD
;
126 base::TimeTicks next_pending_delayed_task
=
127 task_queue_manager_
->NextPendingDelayedTaskRunTime();
128 base::TimeDelta max_long_idle_period_duration
=
129 base::TimeDelta::FromMilliseconds(kMaximumIdlePeriodMillis
);
130 base::TimeDelta long_idle_period_duration
;
131 if (next_pending_delayed_task
.is_null()) {
132 long_idle_period_duration
= max_long_idle_period_duration
;
134 // Limit the idle period duration to be before the next pending task.
135 long_idle_period_duration
= std::min(next_pending_delayed_task
- now
,
136 max_long_idle_period_duration
);
139 if (long_idle_period_duration
> base::TimeDelta()) {
140 *next_long_idle_period_delay_out
= long_idle_period_duration
;
141 return long_idle_period_duration
== max_long_idle_period_duration
142 ? IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
143 : IdlePeriodState::IN_LONG_IDLE_PERIOD
;
145 // If we can't start the idle period yet then try again after wakeup.
146 *next_long_idle_period_delay_out
= base::TimeDelta::FromMilliseconds(
147 kRetryInitiateLongIdlePeriodDelayMillis
);
148 return IdlePeriodState::NOT_IN_IDLE_PERIOD
;
152 void SchedulerHelper::InitiateLongIdlePeriod() {
153 TRACE_EVENT0(disabled_by_default_tracing_category_
, "InitiateLongIdlePeriod");
154 CheckOnValidThread();
156 // End any previous idle period.
159 base::TimeTicks
now(Now());
160 base::TimeDelta next_long_idle_period_delay
;
161 IdlePeriodState new_idle_period_state
=
162 ComputeNewLongIdlePeriodState(now
, &next_long_idle_period_delay
);
163 if (IsInIdlePeriod(new_idle_period_state
)) {
164 StartIdlePeriod(new_idle_period_state
, now
,
165 now
+ next_long_idle_period_delay
,
169 if (task_queue_manager_
->IsQueueEmpty(QueueId::IDLE_TASK_QUEUE
)) {
170 // If there are no current idle tasks then post the call to initiate the
171 // next idle for execution after wakeup (at which point after-wakeup idle
172 // tasks might be eligible to run or more idle tasks posted).
173 control_task_after_wakeup_runner_
->PostDelayedTask(
175 initiate_next_long_idle_period_after_wakeup_closure_
.callback(),
176 next_long_idle_period_delay
);
178 // Otherwise post on the normal control task queue.
179 control_task_runner_
->PostDelayedTask(
180 FROM_HERE
, initiate_next_long_idle_period_closure_
.callback(),
181 next_long_idle_period_delay
);
185 void SchedulerHelper::InitiateLongIdlePeriodAfterWakeup() {
186 TRACE_EVENT0(disabled_by_default_tracing_category_
,
187 "InitiateLongIdlePeriodAfterWakeup");
188 CheckOnValidThread();
190 if (IsInIdlePeriod(idle_period_state_
)) {
191 // Since we were asleep until now, end the async idle period trace event at
192 // the time when it would have ended were we awake.
193 TRACE_EVENT_ASYNC_END_WITH_TIMESTAMP0(
194 tracing_category_
, "RendererSchedulerIdlePeriod", this,
195 std::min(idle_period_deadline_
, Now()).ToInternalValue());
196 idle_period_state_
= IdlePeriodState::ENDING_LONG_IDLE_PERIOD
;
200 // Post a task to initiate the next long idle period rather than calling it
201 // directly to allow all pending PostIdleTaskAfterWakeup tasks to get enqueued
202 // on the idle task queue before the next idle period starts so they are
203 // eligible to be run during the new idle period.
204 control_task_runner_
->PostTask(
205 FROM_HERE
, initiate_next_long_idle_period_closure_
.callback());
208 void SchedulerHelper::StartIdlePeriod(IdlePeriodState new_state
,
210 base::TimeTicks idle_period_deadline
,
211 bool post_end_idle_period
) {
212 DCHECK_GT(idle_period_deadline
, now
);
213 TRACE_EVENT_ASYNC_BEGIN0(tracing_category_
, "RendererSchedulerIdlePeriod",
215 CheckOnValidThread();
216 DCHECK(IsInIdlePeriod(new_state
));
218 task_queue_selector_
->EnableQueue(
219 QueueId::IDLE_TASK_QUEUE
,
220 PrioritizingTaskQueueSelector::BEST_EFFORT_PRIORITY
);
221 task_queue_manager_
->PumpQueue(QueueId::IDLE_TASK_QUEUE
);
222 idle_period_state_
= new_state
;
224 idle_period_deadline_
= idle_period_deadline
;
225 if (post_end_idle_period
) {
226 control_task_runner_
->PostDelayedTask(
228 end_idle_period_closure_
.callback(),
229 idle_period_deadline_
- now
);
233 void SchedulerHelper::EndIdlePeriod() {
234 CheckOnValidThread();
236 end_idle_period_closure_
.Cancel();
237 initiate_next_long_idle_period_closure_
.Cancel();
238 initiate_next_long_idle_period_after_wakeup_closure_
.Cancel();
240 // If we weren't already within an idle period then early-out.
241 if (!IsInIdlePeriod(idle_period_state_
))
244 // If we are in the ENDING_LONG_IDLE_PERIOD state we have already logged the
246 if (idle_period_state_
!= IdlePeriodState::ENDING_LONG_IDLE_PERIOD
) {
248 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
249 if (is_tracing
&& !idle_period_deadline_
.is_null() &&
250 base::TimeTicks::Now() > idle_period_deadline_
) {
251 TRACE_EVENT_ASYNC_STEP_INTO_WITH_TIMESTAMP0(
252 tracing_category_
, "RendererSchedulerIdlePeriod", this,
253 "DeadlineOverrun", idle_period_deadline_
.ToInternalValue());
255 TRACE_EVENT_ASYNC_END0(tracing_category_
, "RendererSchedulerIdlePeriod",
259 task_queue_selector_
->DisableQueue(QueueId::IDLE_TASK_QUEUE
);
260 idle_period_state_
= IdlePeriodState::NOT_IN_IDLE_PERIOD
;
261 idle_period_deadline_
= base::TimeTicks();
265 bool SchedulerHelper::IsInIdlePeriod(IdlePeriodState state
) {
266 return state
!= IdlePeriodState::NOT_IN_IDLE_PERIOD
;
269 bool SchedulerHelper::CanExceedIdleDeadlineIfRequired() const {
270 TRACE_EVENT0(tracing_category_
, "CanExceedIdleDeadlineIfRequired");
271 CheckOnValidThread();
272 return idle_period_state_
==
273 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
;
276 void SchedulerHelper::SetTimeSourceForTesting(
277 scoped_refptr
<cc::TestNowSource
> time_source
) {
278 CheckOnValidThread();
279 time_source_
= time_source
;
280 task_queue_manager_
->SetTimeSourceForTesting(time_source
);
283 void SchedulerHelper::SetWorkBatchSizeForTesting(size_t work_batch_size
) {
284 CheckOnValidThread();
285 task_queue_manager_
->SetWorkBatchSize(work_batch_size
);
288 base::TimeTicks
SchedulerHelper::Now() const {
289 return UNLIKELY(time_source_
) ? time_source_
->Now() : base::TimeTicks::Now();
292 SchedulerHelper::IdlePeriodState
293 SchedulerHelper::SchedulerIdlePeriodState() const {
294 return idle_period_state_
;
297 PrioritizingTaskQueueSelector
*
298 SchedulerHelper::SchedulerTaskQueueSelector() const {
299 return task_queue_selector_
.get();
302 TaskQueueManager
* SchedulerHelper::SchedulerTaskQueueManager() const {
303 return task_queue_manager_
.get();
307 const char* SchedulerHelper::TaskQueueIdToString(QueueId queue_id
) {
309 case DEFAULT_TASK_QUEUE
:
311 case IDLE_TASK_QUEUE
:
313 case CONTROL_TASK_QUEUE
:
315 case CONTROL_TASK_AFTER_WAKEUP_QUEUE
:
316 return "control_after_wakeup_tq";
324 const char* SchedulerHelper::IdlePeriodStateToString(
325 IdlePeriodState idle_period_state
) {
326 switch (idle_period_state
) {
327 case IdlePeriodState::NOT_IN_IDLE_PERIOD
:
328 return "not_in_idle_period";
329 case IdlePeriodState::IN_SHORT_IDLE_PERIOD
:
330 return "in_short_idle_period";
331 case IdlePeriodState::IN_LONG_IDLE_PERIOD
:
332 return "in_long_idle_period";
333 case IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
:
334 return "in_long_idle_period_with_max_deadline";
335 case IdlePeriodState::ENDING_LONG_IDLE_PERIOD
:
336 return "ending_long_idle_period";
343 void SchedulerHelper::AddTaskObserver(
344 base::MessageLoop::TaskObserver
* task_observer
) {
345 CheckOnValidThread();
346 if (task_queue_manager_
)
347 task_queue_manager_
->AddTaskObserver(task_observer
);
350 void SchedulerHelper::RemoveTaskObserver(
351 base::MessageLoop::TaskObserver
* task_observer
) {
352 CheckOnValidThread();
353 if (task_queue_manager_
)
354 task_queue_manager_
->RemoveTaskObserver(task_observer
);
357 } // namespace content