Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / components / scheduler / child / idle_helper.cc
blob07b66e66af7e541c31f9fa126077576a1050f27e
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/idle_helper.h"
7 #include "base/time/time.h"
8 #include "base/trace_event/trace_event.h"
9 #include "base/trace_event/trace_event_argument.h"
10 #include "components/scheduler/child/scheduler_helper.h"
11 #include "components/scheduler/child/task_queue.h"
12 #include "components/scheduler/child/task_queue_manager.h"
14 namespace scheduler {
16 IdleHelper::IdleHelper(
17 SchedulerHelper* helper,
18 Delegate* delegate,
19 const char* tracing_category,
20 const char* disabled_by_default_tracing_category,
21 const char* idle_period_tracing_name,
22 base::TimeDelta required_quiescence_duration_before_long_idle_period)
23 : helper_(helper),
24 delegate_(delegate),
25 idle_queue_(
26 helper_->NewTaskQueue(TaskQueue::Spec("idle_tq").SetPumpPolicy(
27 TaskQueue::PumpPolicy::MANUAL))),
28 state_(helper,
29 delegate,
30 tracing_category,
31 disabled_by_default_tracing_category,
32 idle_period_tracing_name),
33 required_quiescence_duration_before_long_idle_period_(
34 required_quiescence_duration_before_long_idle_period),
35 disabled_by_default_tracing_category_(
36 disabled_by_default_tracing_category),
37 weak_factory_(this) {
38 weak_idle_helper_ptr_ = weak_factory_.GetWeakPtr();
39 enable_next_long_idle_period_closure_.Reset(
40 base::Bind(&IdleHelper::EnableLongIdlePeriod, weak_idle_helper_ptr_));
41 on_idle_task_posted_closure_.Reset(base::Bind(
42 &IdleHelper::OnIdleTaskPostedOnMainThread, weak_idle_helper_ptr_));
44 idle_task_runner_ = make_scoped_refptr(new SingleThreadIdleTaskRunner(
45 idle_queue_, helper_->ControlAfterWakeUpTaskRunner(), this,
46 tracing_category));
48 idle_queue_->SetQueuePriority(TaskQueue::DISABLED_PRIORITY);
50 helper_->AddTaskObserver(this);
53 IdleHelper::~IdleHelper() {
54 helper_->RemoveTaskObserver(this);
57 IdleHelper::Delegate::Delegate() {
60 IdleHelper::Delegate::~Delegate() {
63 scoped_refptr<SingleThreadIdleTaskRunner> IdleHelper::IdleTaskRunner() {
64 helper_->CheckOnValidThread();
65 return idle_task_runner_;
68 IdleHelper::IdlePeriodState IdleHelper::ComputeNewLongIdlePeriodState(
69 const base::TimeTicks now,
70 base::TimeDelta* next_long_idle_period_delay_out) {
71 helper_->CheckOnValidThread();
73 if (!delegate_->CanEnterLongIdlePeriod(now,
74 next_long_idle_period_delay_out)) {
75 return IdlePeriodState::NOT_IN_IDLE_PERIOD;
78 base::TimeTicks next_pending_delayed_task =
79 helper_->NextPendingDelayedTaskRunTime();
80 base::TimeDelta max_long_idle_period_duration =
81 base::TimeDelta::FromMilliseconds(kMaximumIdlePeriodMillis);
82 base::TimeDelta long_idle_period_duration;
83 if (next_pending_delayed_task.is_null()) {
84 long_idle_period_duration = max_long_idle_period_duration;
85 } else {
86 // Limit the idle period duration to be before the next pending task.
87 long_idle_period_duration = std::min(next_pending_delayed_task - now,
88 max_long_idle_period_duration);
91 if (long_idle_period_duration >=
92 base::TimeDelta::FromMilliseconds(kMinimumIdlePeriodDurationMillis)) {
93 *next_long_idle_period_delay_out = long_idle_period_duration;
94 if (idle_queue_->IsQueueEmpty()) {
95 return IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED;
96 } else if (long_idle_period_duration == max_long_idle_period_duration) {
97 return IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE;
98 } else {
99 return IdlePeriodState::IN_LONG_IDLE_PERIOD;
101 } else {
102 // If we can't start the idle period yet then try again after wakeup.
103 *next_long_idle_period_delay_out = base::TimeDelta::FromMilliseconds(
104 kRetryEnableLongIdlePeriodDelayMillis);
105 return IdlePeriodState::NOT_IN_IDLE_PERIOD;
109 bool IdleHelper::ShouldWaitForQuiescence() {
110 helper_->CheckOnValidThread();
112 if (helper_->IsShutdown())
113 return false;
115 if (required_quiescence_duration_before_long_idle_period_ ==
116 base::TimeDelta())
117 return false;
119 bool system_is_quiescent = helper_->GetAndClearSystemIsQuiescentBit();
120 TRACE_EVENT1(disabled_by_default_tracing_category_, "ShouldWaitForQuiescence",
121 "system_is_quiescent", system_is_quiescent);
122 return !system_is_quiescent;
125 void IdleHelper::EnableLongIdlePeriod() {
126 TRACE_EVENT0(disabled_by_default_tracing_category_, "EnableLongIdlePeriod");
127 helper_->CheckOnValidThread();
128 if (helper_->IsShutdown())
129 return;
131 // End any previous idle period.
132 EndIdlePeriod();
134 if (ShouldWaitForQuiescence()) {
135 helper_->ControlTaskRunner()->PostDelayedTask(
136 FROM_HERE, enable_next_long_idle_period_closure_.callback(),
137 required_quiescence_duration_before_long_idle_period_);
138 delegate_->IsNotQuiescent();
139 return;
142 base::TimeTicks now(helper_->Now());
143 base::TimeDelta next_long_idle_period_delay;
144 IdlePeriodState new_idle_period_state =
145 ComputeNewLongIdlePeriodState(now, &next_long_idle_period_delay);
146 if (IsInIdlePeriod(new_idle_period_state)) {
147 StartIdlePeriod(new_idle_period_state, now,
148 now + next_long_idle_period_delay);
149 } else {
150 // Otherwise wait for the next long idle period delay before trying again.
151 helper_->ControlTaskRunner()->PostDelayedTask(
152 FROM_HERE, enable_next_long_idle_period_closure_.callback(),
153 next_long_idle_period_delay);
157 void IdleHelper::StartIdlePeriod(IdlePeriodState new_state,
158 base::TimeTicks now,
159 base::TimeTicks idle_period_deadline) {
160 DCHECK_GT(idle_period_deadline, now);
161 helper_->CheckOnValidThread();
162 DCHECK(IsInIdlePeriod(new_state));
164 base::TimeDelta idle_period_duration(idle_period_deadline - now);
165 if (idle_period_duration <
166 base::TimeDelta::FromMilliseconds(kMinimumIdlePeriodDurationMillis)) {
167 TRACE_EVENT1(disabled_by_default_tracing_category_,
168 "NotStartingIdlePeriodBecauseDeadlineIsTooClose",
169 "idle_period_duration_ms",
170 idle_period_duration.InMillisecondsF());
171 return;
174 TRACE_EVENT0(disabled_by_default_tracing_category_, "StartIdlePeriod");
175 idle_queue_->SetQueuePriority(TaskQueue::BEST_EFFORT_PRIORITY);
176 idle_queue_->PumpQueue();
178 state_.UpdateState(new_state, idle_period_deadline, now);
181 void IdleHelper::EndIdlePeriod() {
182 helper_->CheckOnValidThread();
183 TRACE_EVENT0(disabled_by_default_tracing_category_, "EndIdlePeriod");
185 enable_next_long_idle_period_closure_.Cancel();
186 on_idle_task_posted_closure_.Cancel();
188 // If we weren't already within an idle period then early-out.
189 if (!IsInIdlePeriod(state_.idle_period_state()))
190 return;
192 idle_queue_->SetQueuePriority(TaskQueue::DISABLED_PRIORITY);
193 state_.UpdateState(IdlePeriodState::NOT_IN_IDLE_PERIOD, base::TimeTicks(),
194 base::TimeTicks());
197 void IdleHelper::WillProcessTask(const base::PendingTask& pending_task) {
200 void IdleHelper::DidProcessTask(const base::PendingTask& pending_task) {
201 helper_->CheckOnValidThread();
202 TRACE_EVENT0(disabled_by_default_tracing_category_, "DidProcessTask");
203 if (IsInIdlePeriod(state_.idle_period_state()) &&
204 state_.idle_period_state() !=
205 IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED &&
206 helper_->Now() >= state_.idle_period_deadline()) {
207 // If the idle period deadline has now been reached, either end the idle
208 // period or trigger a new long-idle period.
209 if (IsInLongIdlePeriod(state_.idle_period_state())) {
210 EnableLongIdlePeriod();
211 } else {
212 DCHECK(IdlePeriodState::IN_SHORT_IDLE_PERIOD ==
213 state_.idle_period_state());
214 EndIdlePeriod();
219 void IdleHelper::UpdateLongIdlePeriodStateAfterIdleTask() {
220 helper_->CheckOnValidThread();
221 DCHECK(IsInLongIdlePeriod(state_.idle_period_state()));
222 TRACE_EVENT0(disabled_by_default_tracing_category_,
223 "UpdateLongIdlePeriodStateAfterIdleTask");
224 TaskQueue::QueueState queue_state = idle_queue_->GetQueueState();
225 if (queue_state == TaskQueue::QueueState::EMPTY) {
226 // If there are no more idle tasks then pause long idle period ticks until a
227 // new idle task is posted.
228 state_.UpdateState(IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED,
229 state_.idle_period_deadline(), base::TimeTicks());
230 } else if (queue_state == TaskQueue::QueueState::NEEDS_PUMPING) {
231 // If there is still idle work to do then just start the next idle period.
232 base::TimeDelta next_long_idle_period_delay;
233 if (state_.idle_period_state() ==
234 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE) {
235 // If we are in a max deadline long idle period then start the next
236 // idle period immediately.
237 next_long_idle_period_delay = base::TimeDelta();
238 } else {
239 // Otherwise ensure that we kick the scheduler at the right time to
240 // initiate the next idle period.
241 next_long_idle_period_delay = std::max(
242 base::TimeDelta(), state_.idle_period_deadline() - helper_->Now());
244 if (next_long_idle_period_delay == base::TimeDelta()) {
245 EnableLongIdlePeriod();
246 } else {
247 helper_->ControlTaskRunner()->PostDelayedTask(
248 FROM_HERE, enable_next_long_idle_period_closure_.callback(),
249 next_long_idle_period_delay);
254 base::TimeTicks IdleHelper::CurrentIdleTaskDeadline() const {
255 helper_->CheckOnValidThread();
256 return state_.idle_period_deadline();
259 void IdleHelper::OnIdleTaskPosted() {
260 TRACE_EVENT0(disabled_by_default_tracing_category_, "OnIdleTaskPosted");
261 if (idle_task_runner_->RunsTasksOnCurrentThread()) {
262 OnIdleTaskPostedOnMainThread();
263 } else {
264 helper_->ControlTaskRunner()->PostTask(
265 FROM_HERE, on_idle_task_posted_closure_.callback());
269 void IdleHelper::OnIdleTaskPostedOnMainThread() {
270 TRACE_EVENT0(disabled_by_default_tracing_category_,
271 "OnIdleTaskPostedOnMainThread");
272 if (state_.idle_period_state() ==
273 IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED) {
274 // Restart long idle period ticks.
275 helper_->ControlTaskRunner()->PostTask(
276 FROM_HERE, enable_next_long_idle_period_closure_.callback());
280 base::TimeTicks IdleHelper::WillProcessIdleTask() {
281 helper_->CheckOnValidThread();
282 DCHECK(IsInIdlePeriod(state_.idle_period_state()));
284 state_.TraceIdleIdleTaskStart();
285 return CurrentIdleTaskDeadline();
288 void IdleHelper::DidProcessIdleTask() {
289 helper_->CheckOnValidThread();
290 state_.TraceIdleIdleTaskEnd();
291 if (IsInLongIdlePeriod(state_.idle_period_state())) {
292 UpdateLongIdlePeriodStateAfterIdleTask();
296 // static
297 bool IdleHelper::IsInIdlePeriod(IdlePeriodState state) {
298 return state != IdlePeriodState::NOT_IN_IDLE_PERIOD;
301 // static
302 bool IdleHelper::IsInLongIdlePeriod(IdlePeriodState state) {
303 return state == IdlePeriodState::IN_LONG_IDLE_PERIOD ||
304 state == IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE ||
305 state == IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED;
308 bool IdleHelper::CanExceedIdleDeadlineIfRequired() const {
309 TRACE_EVENT0(disabled_by_default_tracing_category_,
310 "CanExceedIdleDeadlineIfRequired");
311 helper_->CheckOnValidThread();
312 return state_.idle_period_state() ==
313 IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE;
316 IdleHelper::IdlePeriodState IdleHelper::SchedulerIdlePeriodState() const {
317 return state_.idle_period_state();
320 IdleHelper::State::State(SchedulerHelper* helper,
321 Delegate* delegate,
322 const char* tracing_category,
323 const char* disabled_by_default_tracing_category,
324 const char* idle_period_tracing_name)
325 : helper_(helper),
326 delegate_(delegate),
327 idle_period_state_(IdlePeriodState::NOT_IN_IDLE_PERIOD),
328 nestable_events_started_(false),
329 tracing_category_(tracing_category),
330 disabled_by_default_tracing_category_(
331 disabled_by_default_tracing_category),
332 idle_period_tracing_name_(idle_period_tracing_name) {
335 IdleHelper::State::~State() {
338 IdleHelper::IdlePeriodState IdleHelper::State::idle_period_state() const {
339 helper_->CheckOnValidThread();
340 return idle_period_state_;
343 base::TimeTicks IdleHelper::State::idle_period_deadline() const {
344 helper_->CheckOnValidThread();
345 return idle_period_deadline_;
348 void IdleHelper::State::UpdateState(IdlePeriodState new_state,
349 base::TimeTicks new_deadline,
350 base::TimeTicks optional_now) {
351 IdlePeriodState old_idle_period_state = idle_period_state_;
353 helper_->CheckOnValidThread();
354 if (new_state == idle_period_state_) {
355 DCHECK_EQ(new_deadline, idle_period_deadline_);
356 return;
359 bool is_tracing;
360 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_, &is_tracing);
361 if (is_tracing) {
362 base::TimeTicks now(optional_now.is_null() ? helper_->Now() : optional_now);
363 TraceEventIdlePeriodStateChange(new_state, new_deadline, now);
364 idle_period_deadline_for_tracing_ =
365 base::TraceTicks::Now() + (new_deadline - now);
368 idle_period_state_ = new_state;
369 idle_period_deadline_ = new_deadline;
371 // Inform the delegate if we are starting or ending an idle period.
372 if (IsInIdlePeriod(new_state) && !IsInIdlePeriod(old_idle_period_state)) {
373 delegate_->OnIdlePeriodStarted();
374 } else if (!IsInIdlePeriod(new_state) &&
375 IsInIdlePeriod(old_idle_period_state)) {
376 delegate_->OnIdlePeriodEnded();
380 void IdleHelper::State::TraceIdleIdleTaskStart() {
381 helper_->CheckOnValidThread();
383 bool is_tracing;
384 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_, &is_tracing);
385 if (is_tracing && nestable_events_started_) {
386 last_idle_task_trace_time_ = base::TraceTicks::Now();
387 TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
388 tracing_category_, "RunningIdleTask", this,
389 last_idle_task_trace_time_.ToInternalValue());
393 void IdleHelper::State::TraceIdleIdleTaskEnd() {
394 helper_->CheckOnValidThread();
396 bool is_tracing;
397 TRACE_EVENT_CATEGORY_GROUP_ENABLED(tracing_category_, &is_tracing);
398 if (is_tracing && nestable_events_started_) {
399 if (!idle_period_deadline_for_tracing_.is_null() &&
400 base::TraceTicks::Now() > idle_period_deadline_for_tracing_) {
401 TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
402 tracing_category_, "DeadlineOverrun", this,
403 std::max(idle_period_deadline_for_tracing_,
404 last_idle_task_trace_time_).ToInternalValue());
405 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_, "DeadlineOverrun",
406 this);
408 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_, "RunningIdleTask", this);
412 void IdleHelper::State::TraceEventIdlePeriodStateChange(
413 IdlePeriodState new_state,
414 base::TimeTicks new_deadline,
415 base::TimeTicks now) {
416 TRACE_EVENT2(disabled_by_default_tracing_category_, "SetIdlePeriodState",
417 "old_state",
418 IdleHelper::IdlePeriodStateToString(idle_period_state_),
419 "new_state", IdleHelper::IdlePeriodStateToString(new_state));
420 if (nestable_events_started_) {
421 // End async tracing events for the state we are leaving.
422 if (idle_period_state_ == IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED) {
423 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_, "LongIdlePeriodPaused",
424 this);
426 if (IsInLongIdlePeriod(idle_period_state_) &&
427 !IsInLongIdlePeriod(new_state)) {
428 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_, "LongIdlePeriod",
429 this);
431 if (idle_period_state_ == IdlePeriodState::IN_SHORT_IDLE_PERIOD) {
432 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_, "ShortIdlePeriod",
433 this);
435 if (IsInIdlePeriod(idle_period_state_) && !IsInIdlePeriod(new_state)) {
436 TRACE_EVENT_NESTABLE_ASYNC_END0(tracing_category_,
437 idle_period_tracing_name_, this);
438 nestable_events_started_ = false;
442 // Start async tracing events for the state we are entering.
443 if (IsInIdlePeriod(new_state) && !IsInIdlePeriod(idle_period_state_)) {
444 nestable_events_started_ = true;
445 TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
446 tracing_category_, idle_period_tracing_name_, this,
447 "idle_period_length_ms", (new_deadline - now).ToInternalValue());
449 if (new_state == IdlePeriodState::IN_SHORT_IDLE_PERIOD) {
450 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(tracing_category_, "ShortIdlePeriod",
451 this);
453 if (IsInLongIdlePeriod(new_state) &&
454 !IsInLongIdlePeriod(idle_period_state_)) {
455 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(tracing_category_, "LongIdlePeriod",
456 this);
458 if (new_state == IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED) {
459 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(tracing_category_, "LongIdlePeriodPaused",
460 this);
464 // static
465 const char* IdleHelper::IdlePeriodStateToString(
466 IdlePeriodState idle_period_state) {
467 switch (idle_period_state) {
468 case IdlePeriodState::NOT_IN_IDLE_PERIOD:
469 return "not_in_idle_period";
470 case IdlePeriodState::IN_SHORT_IDLE_PERIOD:
471 return "in_short_idle_period";
472 case IdlePeriodState::IN_LONG_IDLE_PERIOD:
473 return "in_long_idle_period";
474 case IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE:
475 return "in_long_idle_period_with_max_deadline";
476 case IdlePeriodState::IN_LONG_IDLE_PERIOD_PAUSED:
477 return "in_long_idle_period_paused";
478 default:
479 NOTREACHED();
480 return nullptr;
484 } // namespace scheduler