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 "components/scheduler/child/scheduler_helper.h"
7 #include "base/trace_event/trace_event.h"
8 #include "base/trace_event/trace_event_argument.h"
9 #include "components/scheduler/child/nestable_single_thread_task_runner.h"
10 #include "components/scheduler/child/prioritizing_task_queue_selector.h"
11 #include "components/scheduler/child/time_source.h"
15 SchedulerHelper::SchedulerHelper(
16 scoped_refptr
<NestableSingleThreadTaskRunner
> main_task_runner
,
17 SchedulerHelperDelegate
* scheduler_helper_delegate
,
18 const char* tracing_category
,
19 const char* disabled_by_default_tracing_category
,
20 const char* idle_period_tracing_name
,
21 size_t total_task_queue_count
,
22 base::TimeDelta required_quiescence_duration_before_long_idle_period
)
23 : task_queue_selector_(new PrioritizingTaskQueueSelector()),
25 new TaskQueueManager(total_task_queue_count
,
27 task_queue_selector_
.get(),
28 disabled_by_default_tracing_category
)),
29 idle_period_state_(IdlePeriodState::NOT_IN_IDLE_PERIOD
),
30 scheduler_helper_delegate_(scheduler_helper_delegate
),
32 task_queue_manager_
->TaskRunnerForQueue(QueueId::CONTROL_TASK_QUEUE
)),
33 control_task_after_wakeup_runner_(task_queue_manager_
->TaskRunnerForQueue(
34 QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
)),
36 task_queue_manager_
->TaskRunnerForQueue(QueueId::DEFAULT_TASK_QUEUE
)),
37 quiescence_monitored_task_queue_mask_(
38 ((1ull << total_task_queue_count
) - 1ull) &
39 ~(1ull << QueueId::IDLE_TASK_QUEUE
) &
40 ~(1ull << QueueId::CONTROL_TASK_QUEUE
) &
41 ~(1ull << QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
)),
42 required_quiescence_duration_before_long_idle_period_(
43 required_quiescence_duration_before_long_idle_period
),
44 time_source_(new TimeSource
),
45 tracing_category_(tracing_category
),
46 disabled_by_default_tracing_category_(
47 disabled_by_default_tracing_category
),
48 idle_period_tracing_name_(idle_period_tracing_name
),
50 DCHECK_GE(total_task_queue_count
,
51 static_cast<size_t>(QueueId::TASK_QUEUE_COUNT
));
52 weak_scheduler_ptr_
= weak_factory_
.GetWeakPtr();
53 end_idle_period_closure_
.Reset(
54 base::Bind(&SchedulerHelper::EndIdlePeriod
, weak_scheduler_ptr_
));
55 enable_next_long_idle_period_closure_
.Reset(
56 base::Bind(&SchedulerHelper::EnableLongIdlePeriod
, weak_scheduler_ptr_
));
57 enable_next_long_idle_period_after_wakeup_closure_
.Reset(base::Bind(
58 &SchedulerHelper::EnableLongIdlePeriodAfterWakeup
, weak_scheduler_ptr_
));
60 idle_task_runner_
= make_scoped_refptr(new SingleThreadIdleTaskRunner(
61 task_queue_manager_
->TaskRunnerForQueue(QueueId::IDLE_TASK_QUEUE
),
62 control_task_after_wakeup_runner_
,
63 base::Bind(&SchedulerHelper::CurrentIdleTaskDeadlineCallback
,
67 task_queue_selector_
->SetQueuePriority(
68 QueueId::CONTROL_TASK_QUEUE
,
69 PrioritizingTaskQueueSelector::CONTROL_PRIORITY
);
71 task_queue_selector_
->SetQueuePriority(
72 QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
,
73 PrioritizingTaskQueueSelector::CONTROL_PRIORITY
);
74 task_queue_manager_
->SetPumpPolicy(
75 QueueId::CONTROL_TASK_AFTER_WAKEUP_QUEUE
,
76 TaskQueueManager::PumpPolicy::AFTER_WAKEUP
);
78 task_queue_selector_
->DisableQueue(QueueId::IDLE_TASK_QUEUE
);
79 task_queue_manager_
->SetPumpPolicy(QueueId::IDLE_TASK_QUEUE
,
80 TaskQueueManager::PumpPolicy::MANUAL
);
82 for (size_t i
= 0; i
< TASK_QUEUE_COUNT
; i
++) {
83 task_queue_manager_
->SetQueueName(
84 i
, TaskQueueIdToString(static_cast<QueueId
>(i
)));
87 // TODO(skyostil): Increase this to 4 (crbug.com/444764).
88 task_queue_manager_
->SetWorkBatchSize(1);
91 SchedulerHelper::~SchedulerHelper() {
94 SchedulerHelper::SchedulerHelperDelegate::SchedulerHelperDelegate() {
97 SchedulerHelper::SchedulerHelperDelegate::~SchedulerHelperDelegate() {
100 void SchedulerHelper::Shutdown() {
101 CheckOnValidThread();
102 task_queue_manager_
.reset();
105 scoped_refptr
<base::SingleThreadTaskRunner
>
106 SchedulerHelper::DefaultTaskRunner() {
107 CheckOnValidThread();
108 return default_task_runner_
;
111 scoped_refptr
<SingleThreadIdleTaskRunner
> SchedulerHelper::IdleTaskRunner() {
112 CheckOnValidThread();
113 return idle_task_runner_
;
116 scoped_refptr
<base::SingleThreadTaskRunner
>
117 SchedulerHelper::ControlTaskRunner() {
118 return control_task_runner_
;
121 void SchedulerHelper::CurrentIdleTaskDeadlineCallback(
122 base::TimeTicks
* deadline_out
) const {
123 CheckOnValidThread();
124 *deadline_out
= idle_period_deadline_
;
127 SchedulerHelper::IdlePeriodState
SchedulerHelper::ComputeNewLongIdlePeriodState(
128 const base::TimeTicks now
,
129 base::TimeDelta
* next_long_idle_period_delay_out
) {
130 CheckOnValidThread();
132 if (!scheduler_helper_delegate_
->CanEnterLongIdlePeriod(
133 now
, next_long_idle_period_delay_out
)) {
134 return IdlePeriodState::NOT_IN_IDLE_PERIOD
;
137 base::TimeTicks next_pending_delayed_task
=
138 task_queue_manager_
->NextPendingDelayedTaskRunTime();
139 base::TimeDelta max_long_idle_period_duration
=
140 base::TimeDelta::FromMilliseconds(kMaximumIdlePeriodMillis
);
141 base::TimeDelta long_idle_period_duration
;
142 if (next_pending_delayed_task
.is_null()) {
143 long_idle_period_duration
= max_long_idle_period_duration
;
145 // Limit the idle period duration to be before the next pending task.
146 long_idle_period_duration
= std::min(next_pending_delayed_task
- now
,
147 max_long_idle_period_duration
);
150 if (long_idle_period_duration
> base::TimeDelta()) {
151 *next_long_idle_period_delay_out
= long_idle_period_duration
;
152 return long_idle_period_duration
== max_long_idle_period_duration
153 ? IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
154 : IdlePeriodState::IN_LONG_IDLE_PERIOD
;
156 // If we can't start the idle period yet then try again after wakeup.
157 *next_long_idle_period_delay_out
= base::TimeDelta::FromMilliseconds(
158 kRetryEnableLongIdlePeriodDelayMillis
);
159 return IdlePeriodState::NOT_IN_IDLE_PERIOD
;
163 bool SchedulerHelper::ShouldWaitForQuiescence() {
164 CheckOnValidThread();
166 if (!task_queue_manager_
)
169 if (required_quiescence_duration_before_long_idle_period_
==
173 uint64 task_queues_run_since_last_check_bitmap
=
174 task_queue_manager_
->GetAndClearTaskWasRunOnQueueBitmap() &
175 quiescence_monitored_task_queue_mask_
;
177 TRACE_EVENT1(disabled_by_default_tracing_category_
, "ShouldWaitForQuiescence",
178 "task_queues_run_since_last_check_bitmap",
179 task_queues_run_since_last_check_bitmap
);
181 // If anything was run on the queues we care about, then we're not quiescent
182 // and we should wait.
183 return task_queues_run_since_last_check_bitmap
!= 0;
186 void SchedulerHelper::EnableLongIdlePeriod() {
187 TRACE_EVENT0(disabled_by_default_tracing_category_
, "EnableLongIdlePeriod");
188 CheckOnValidThread();
190 // End any previous idle period.
193 if (ShouldWaitForQuiescence()) {
194 control_task_runner_
->PostDelayedTask(
195 FROM_HERE
, enable_next_long_idle_period_closure_
.callback(),
196 required_quiescence_duration_before_long_idle_period_
);
197 scheduler_helper_delegate_
->IsNotQuiescent();
201 base::TimeTicks
now(Now());
202 base::TimeDelta next_long_idle_period_delay
;
203 IdlePeriodState new_idle_period_state
=
204 ComputeNewLongIdlePeriodState(now
, &next_long_idle_period_delay
);
205 if (IsInIdlePeriod(new_idle_period_state
)) {
206 StartIdlePeriod(new_idle_period_state
, now
,
207 now
+ next_long_idle_period_delay
, false);
210 if (task_queue_manager_
->IsQueueEmpty(QueueId::IDLE_TASK_QUEUE
)) {
211 // If there are no current idle tasks then post the call to initiate the
212 // next idle for execution after wakeup (at which point after-wakeup idle
213 // tasks might be eligible to run or more idle tasks posted).
214 control_task_after_wakeup_runner_
->PostDelayedTask(
216 enable_next_long_idle_period_after_wakeup_closure_
.callback(),
217 next_long_idle_period_delay
);
219 // Otherwise post on the normal control task queue.
220 control_task_runner_
->PostDelayedTask(
221 FROM_HERE
, enable_next_long_idle_period_closure_
.callback(),
222 next_long_idle_period_delay
);
226 void SchedulerHelper::EnableLongIdlePeriodAfterWakeup() {
227 TRACE_EVENT0(disabled_by_default_tracing_category_
,
228 "EnableLongIdlePeriodAfterWakeup");
229 CheckOnValidThread();
231 if (IsInIdlePeriod(idle_period_state_
)) {
232 // Since we were asleep until now, end the async idle period trace event at
233 // the time when it would have ended were we awake.
234 TRACE_EVENT_ASYNC_END_WITH_TIMESTAMP0(
235 tracing_category_
, idle_period_tracing_name_
, this,
236 std::min(idle_period_deadline_
, Now()).ToInternalValue());
237 idle_period_state_
= IdlePeriodState::ENDING_LONG_IDLE_PERIOD
;
241 // Post a task to initiate the next long idle period rather than calling it
242 // directly to allow all pending PostIdleTaskAfterWakeup tasks to get enqueued
243 // on the idle task queue before the next idle period starts so they are
244 // eligible to be run during the new idle period.
245 control_task_runner_
->PostTask(
246 FROM_HERE
, enable_next_long_idle_period_closure_
.callback());
249 void SchedulerHelper::StartIdlePeriod(IdlePeriodState new_state
,
251 base::TimeTicks idle_period_deadline
,
252 bool post_end_idle_period
) {
253 DCHECK_GT(idle_period_deadline
, now
);
254 TRACE_EVENT_ASYNC_BEGIN0(tracing_category_
, idle_period_tracing_name_
, this);
255 CheckOnValidThread();
256 DCHECK(IsInIdlePeriod(new_state
));
258 task_queue_selector_
->EnableQueue(
259 QueueId::IDLE_TASK_QUEUE
,
260 PrioritizingTaskQueueSelector::BEST_EFFORT_PRIORITY
);
261 task_queue_manager_
->PumpQueue(QueueId::IDLE_TASK_QUEUE
);
262 idle_period_state_
= new_state
;
264 idle_period_deadline_
= idle_period_deadline
;
265 if (post_end_idle_period
) {
266 control_task_runner_
->PostDelayedTask(FROM_HERE
,
267 end_idle_period_closure_
.callback(),
268 idle_period_deadline_
- now
);
272 void SchedulerHelper::EndIdlePeriod() {
273 CheckOnValidThread();
275 end_idle_period_closure_
.Cancel();
276 enable_next_long_idle_period_closure_
.Cancel();
277 enable_next_long_idle_period_after_wakeup_closure_
.Cancel();
279 // If we weren't already within an idle period then early-out.
280 if (!IsInIdlePeriod(idle_period_state_
))
283 // If we are in the ENDING_LONG_IDLE_PERIOD state we have already logged the
285 if (idle_period_state_
!= IdlePeriodState::ENDING_LONG_IDLE_PERIOD
) {
287 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_
, &is_tracing
);
288 if (is_tracing
&& !idle_period_deadline_
.is_null() &&
289 base::TimeTicks::Now() > idle_period_deadline_
) {
290 TRACE_EVENT_ASYNC_STEP_INTO_WITH_TIMESTAMP0(
291 tracing_category_
, idle_period_tracing_name_
, this, "DeadlineOverrun",
292 idle_period_deadline_
.ToInternalValue());
294 TRACE_EVENT_ASYNC_END0(tracing_category_
, idle_period_tracing_name_
, this);
297 task_queue_selector_
->DisableQueue(QueueId::IDLE_TASK_QUEUE
);
298 idle_period_state_
= IdlePeriodState::NOT_IN_IDLE_PERIOD
;
299 idle_period_deadline_
= base::TimeTicks();
303 bool SchedulerHelper::IsInIdlePeriod(IdlePeriodState state
) {
304 return state
!= IdlePeriodState::NOT_IN_IDLE_PERIOD
;
307 bool SchedulerHelper::CanExceedIdleDeadlineIfRequired() const {
308 TRACE_EVENT0(tracing_category_
, "CanExceedIdleDeadlineIfRequired");
309 CheckOnValidThread();
310 return idle_period_state_
==
311 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
;
314 void SchedulerHelper::SetTimeSourceForTesting(
315 scoped_ptr
<TimeSource
> time_source
) {
316 CheckOnValidThread();
317 time_source_
= time_source
.Pass();
320 void SchedulerHelper::SetWorkBatchSizeForTesting(size_t work_batch_size
) {
321 CheckOnValidThread();
322 task_queue_manager_
->SetWorkBatchSize(work_batch_size
);
325 TaskQueueManager
* SchedulerHelper::GetTaskQueueManagerForTesting() {
326 CheckOnValidThread();
327 return task_queue_manager_
.get();
330 base::TimeTicks
SchedulerHelper::Now() const {
331 return time_source_
->Now();
334 SchedulerHelper::IdlePeriodState
SchedulerHelper::SchedulerIdlePeriodState()
336 return idle_period_state_
;
339 PrioritizingTaskQueueSelector
* SchedulerHelper::SchedulerTaskQueueSelector()
341 return task_queue_selector_
.get();
344 scoped_refptr
<base::SingleThreadTaskRunner
> SchedulerHelper::TaskRunnerForQueue(
345 size_t queue_index
) const {
346 CheckOnValidThread();
347 return task_queue_manager_
->TaskRunnerForQueue(queue_index
);
350 void SchedulerHelper::SetQueueName(size_t queue_index
, const char* name
) {
351 CheckOnValidThread();
352 task_queue_manager_
->SetQueueName(queue_index
, name
);
355 bool SchedulerHelper::IsQueueEmpty(size_t queue_index
) const {
356 CheckOnValidThread();
357 return task_queue_manager_
->IsQueueEmpty(queue_index
);
361 const char* SchedulerHelper::TaskQueueIdToString(QueueId queue_id
) {
363 case DEFAULT_TASK_QUEUE
:
365 case IDLE_TASK_QUEUE
:
367 case CONTROL_TASK_QUEUE
:
369 case CONTROL_TASK_AFTER_WAKEUP_QUEUE
:
370 return "control_after_wakeup_tq";
378 const char* SchedulerHelper::IdlePeriodStateToString(
379 IdlePeriodState idle_period_state
) {
380 switch (idle_period_state
) {
381 case IdlePeriodState::NOT_IN_IDLE_PERIOD
:
382 return "not_in_idle_period";
383 case IdlePeriodState::IN_SHORT_IDLE_PERIOD
:
384 return "in_short_idle_period";
385 case IdlePeriodState::IN_LONG_IDLE_PERIOD
:
386 return "in_long_idle_period";
387 case IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE
:
388 return "in_long_idle_period_with_max_deadline";
389 case IdlePeriodState::ENDING_LONG_IDLE_PERIOD
:
390 return "ending_long_idle_period";
397 void SchedulerHelper::AddTaskObserver(
398 base::MessageLoop::TaskObserver
* task_observer
) {
399 CheckOnValidThread();
400 if (task_queue_manager_
)
401 task_queue_manager_
->AddTaskObserver(task_observer
);
404 void SchedulerHelper::RemoveTaskObserver(
405 base::MessageLoop::TaskObserver
* task_observer
) {
406 CheckOnValidThread();
407 if (task_queue_manager_
)
408 task_queue_manager_
->RemoveTaskObserver(task_observer
);
411 } // namespace scheduler