blob: a585657d474a54a839093cee1cc79a41d662eecb [file] [log] [blame]
// 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 "third_party/blink/renderer/platform/scheduler/common/idle_helper.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "third_party/blink/renderer/platform/scheduler/base/real_time_domain.h"
#include "third_party/blink/renderer/platform/scheduler/base/task_queue.h"
#include "third_party/blink/renderer/platform/scheduler/base/task_queue_manager.h"
#include "third_party/blink/renderer/platform/scheduler/child/task_queue_with_task_type.h"
#include "third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h"
namespace blink {
namespace scheduler {
using base::sequence_manager::TaskQueue;
IdleHelper::IdleHelper(
SchedulerHelper* helper,
Delegate* delegate,
const char* idle_period_tracing_name,
base::TimeDelta required_quiescence_duration_before_long_idle_period,
scoped_refptr<TaskQueue> idle_queue)
: helper_(helper),
delegate_(delegate),
idle_queue_(std::move(idle_queue)),
state_(helper, delegate, idle_period_tracing_name),
required_quiescence_duration_before_long_idle_period_(
required_quiescence_duration_before_long_idle_period),
is_shutdown_(false),
weak_factory_(this) {
weak_idle_helper_ptr_ = weak_factory_.GetWeakPtr();
enable_next_long_idle_period_closure_.Reset(base::BindRepeating(
&IdleHelper::EnableLongIdlePeriod, weak_idle_helper_ptr_));
on_idle_task_posted_closure_.Reset(base::BindRepeating(
&IdleHelper::OnIdleTaskPostedOnMainThread, weak_idle_helper_ptr_));
idle_task_runner_ = base::MakeRefCounted<SingleThreadIdleTaskRunner>(
TaskQueueWithTaskType::Create(idle_queue_,
TaskType::kMainThreadTaskQueueIdle),
this);
// This fence will block any idle tasks from running.
idle_queue_->InsertFence(TaskQueue::InsertFencePosition::kBeginningOfTime);
idle_queue_->SetQueuePriority(TaskQueue::kBestEffortPriority);
}
IdleHelper::~IdleHelper() {
Shutdown();
}
void IdleHelper::Shutdown() {
if (is_shutdown_)
return;
EndIdlePeriod();
is_shutdown_ = true;
weak_factory_.InvalidateWeakPtrs();
// Belt & braces, might not be needed.
idle_queue_->ShutdownTaskQueue();
}
IdleHelper::Delegate::Delegate() = default;
IdleHelper::Delegate::~Delegate() = default;
scoped_refptr<SingleThreadIdleTaskRunner> IdleHelper::IdleTaskRunner() {
helper_->CheckOnValidThread();
return idle_task_runner_;
}
IdleHelper::IdlePeriodState IdleHelper::ComputeNewLongIdlePeriodState(
const base::TimeTicks now,
base::TimeDelta* next_long_idle_period_delay_out) {
helper_->CheckOnValidThread();
if (!delegate_->CanEnterLongIdlePeriod(now,
next_long_idle_period_delay_out)) {
return IdlePeriodState::kNotInIdlePeriod;
}
base::TimeTicks next_pending_delayed_task;
base::TimeDelta max_long_idle_period_duration =
base::TimeDelta::FromMilliseconds(kMaximumIdlePeriodMillis);
base::TimeDelta long_idle_period_duration;
if (helper_->real_time_domain()->NextScheduledRunTime(
&next_pending_delayed_task)) {
// Limit the idle period duration to be before the next pending task.
long_idle_period_duration = std::min(next_pending_delayed_task - now,
max_long_idle_period_duration);
} else {
long_idle_period_duration = max_long_idle_period_duration;
}
if (long_idle_period_duration >=
base::TimeDelta::FromMilliseconds(kMinimumIdlePeriodDurationMillis)) {
*next_long_idle_period_delay_out = long_idle_period_duration;
if (!idle_queue_->HasTaskToRunImmediately())
return IdlePeriodState::kInLongIdlePeriodPaused;
if (long_idle_period_duration == max_long_idle_period_duration)
return IdlePeriodState::kInLongIdlePeriodWithMaxDeadline;
return IdlePeriodState::kInLongIdlePeriod;
} else {
// If we can't start the idle period yet then try again after wake-up.
*next_long_idle_period_delay_out = base::TimeDelta::FromMilliseconds(
kRetryEnableLongIdlePeriodDelayMillis);
return IdlePeriodState::kNotInIdlePeriod;
}
}
bool IdleHelper::ShouldWaitForQuiescence() {
helper_->CheckOnValidThread();
if (required_quiescence_duration_before_long_idle_period_ ==
base::TimeDelta()) {
return false;
}
bool system_is_quiescent = helper_->GetAndClearSystemIsQuiescentBit();
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"ShouldWaitForQuiescence", "system_is_quiescent",
system_is_quiescent);
return !system_is_quiescent;
}
void IdleHelper::EnableLongIdlePeriod() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"EnableLongIdlePeriod");
helper_->CheckOnValidThread();
if (is_shutdown_)
return;
// End any previous idle period.
EndIdlePeriod();
if (ShouldWaitForQuiescence()) {
helper_->ControlTaskQueue()->PostDelayedTask(
FROM_HERE, enable_next_long_idle_period_closure_.GetCallback(),
required_quiescence_duration_before_long_idle_period_);
delegate_->IsNotQuiescent();
return;
}
base::TimeTicks now(helper_->NowTicks());
base::TimeDelta next_long_idle_period_delay;
IdlePeriodState new_idle_period_state =
ComputeNewLongIdlePeriodState(now, &next_long_idle_period_delay);
if (IsInIdlePeriod(new_idle_period_state)) {
StartIdlePeriod(new_idle_period_state, now,
now + next_long_idle_period_delay);
} else {
// Otherwise wait for the next long idle period delay before trying again.
helper_->ControlTaskQueue()->PostDelayedTask(
FROM_HERE, enable_next_long_idle_period_closure_.GetCallback(),
next_long_idle_period_delay);
}
}
void IdleHelper::StartIdlePeriod(IdlePeriodState new_state,
base::TimeTicks now,
base::TimeTicks idle_period_deadline) {
DCHECK(!is_shutdown_);
DCHECK_GT(idle_period_deadline, now);
helper_->CheckOnValidThread();
DCHECK(IsInIdlePeriod(new_state));
// Allow any ready delayed idle tasks to run.
idle_task_runner_->EnqueueReadyDelayedIdleTasks();
base::TimeDelta idle_period_duration(idle_period_deadline - now);
if (idle_period_duration <
base::TimeDelta::FromMilliseconds(kMinimumIdlePeriodDurationMillis)) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"NotStartingIdlePeriodBecauseDeadlineIsTooClose",
"idle_period_duration_ms",
idle_period_duration.InMillisecondsF());
return;
}
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"StartIdlePeriod");
if (!IsInIdlePeriod(state_.idle_period_state()))
helper_->AddTaskObserver(this);
// Use a fence to make sure any idle tasks posted after this point do not run
// until the next idle period and unblock existing tasks.
idle_queue_->InsertFence(TaskQueue::InsertFencePosition::kNow);
state_.UpdateState(new_state, idle_period_deadline, now);
}
void IdleHelper::EndIdlePeriod() {
if (is_shutdown_)
return;
helper_->CheckOnValidThread();
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"EndIdlePeriod");
enable_next_long_idle_period_closure_.Cancel();
on_idle_task_posted_closure_.Cancel();
// If we weren't already within an idle period then early-out.
if (!IsInIdlePeriod(state_.idle_period_state()))
return;
helper_->RemoveTaskObserver(this);
// This fence will block any idle tasks from running.
idle_queue_->InsertFence(TaskQueue::InsertFencePosition::kBeginningOfTime);
state_.UpdateState(IdlePeriodState::kNotInIdlePeriod, base::TimeTicks(),
base::TimeTicks());
}
void IdleHelper::WillProcessTask(const base::PendingTask& pending_task) {
DCHECK(!is_shutdown_);
}
void IdleHelper::DidProcessTask(const base::PendingTask& pending_task) {
helper_->CheckOnValidThread();
DCHECK(!is_shutdown_);
DCHECK(IsInIdlePeriod(state_.idle_period_state()));
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"DidProcessTask");
if (state_.idle_period_state() != IdlePeriodState::kInLongIdlePeriodPaused &&
helper_->NowTicks() >= state_.idle_period_deadline()) {
// If the idle period deadline has now been reached, either end the idle
// period or trigger a new long-idle period.
if (IsInLongIdlePeriod(state_.idle_period_state())) {
EnableLongIdlePeriod();
} else {
DCHECK(IdlePeriodState::kInShortIdlePeriod == state_.idle_period_state());
EndIdlePeriod();
}
}
}
void IdleHelper::UpdateLongIdlePeriodStateAfterIdleTask() {
helper_->CheckOnValidThread();
DCHECK(!is_shutdown_);
DCHECK(IsInLongIdlePeriod(state_.idle_period_state()));
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"UpdateLongIdlePeriodStateAfterIdleTask");
if (!idle_queue_->HasTaskToRunImmediately()) {
// If there are no more idle tasks then pause long idle period ticks until a
// new idle task is posted.
state_.UpdateState(IdlePeriodState::kInLongIdlePeriodPaused,
state_.idle_period_deadline(), base::TimeTicks());
} else if (idle_queue_->BlockedByFence()) {
// If there is still idle work to do then just start the next idle period.
base::TimeDelta next_long_idle_period_delay;
// Ensure that we kick the scheduler at the right time to
// initiate the next idle period.
next_long_idle_period_delay = std::max(
base::TimeDelta(), state_.idle_period_deadline() - helper_->NowTicks());
if (next_long_idle_period_delay.is_zero()) {
EnableLongIdlePeriod();
} else {
helper_->ControlTaskQueue()->PostDelayedTask(
FROM_HERE, enable_next_long_idle_period_closure_.GetCallback(),
next_long_idle_period_delay);
}
}
}
base::TimeTicks IdleHelper::CurrentIdleTaskDeadline() const {
helper_->CheckOnValidThread();
return state_.idle_period_deadline();
}
void IdleHelper::OnIdleTaskPosted() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"OnIdleTaskPosted");
if (is_shutdown_)
return;
if (idle_task_runner_->RunsTasksInCurrentSequence()) {
OnIdleTaskPostedOnMainThread();
} else {
helper_->ControlTaskQueue()->PostTask(
FROM_HERE, on_idle_task_posted_closure_.GetCallback());
}
}
void IdleHelper::OnIdleTaskPostedOnMainThread() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"OnIdleTaskPostedOnMainThread");
if (is_shutdown_)
return;
delegate_->OnPendingTasksChanged(true);
if (state_.idle_period_state() == IdlePeriodState::kInLongIdlePeriodPaused) {
// Restart long idle period ticks.
helper_->ControlTaskQueue()->PostTask(
FROM_HERE, enable_next_long_idle_period_closure_.GetCallback());
}
}
base::TimeTicks IdleHelper::WillProcessIdleTask() {
helper_->CheckOnValidThread();
DCHECK(!is_shutdown_);
state_.TraceIdleIdleTaskStart();
return CurrentIdleTaskDeadline();
}
void IdleHelper::DidProcessIdleTask() {
helper_->CheckOnValidThread();
if (is_shutdown_)
return;
state_.TraceIdleIdleTaskEnd();
if (IsInLongIdlePeriod(state_.idle_period_state())) {
UpdateLongIdlePeriodStateAfterIdleTask();
}
delegate_->OnPendingTasksChanged(idle_queue_->GetNumberOfPendingTasks() > 0);
}
base::TimeTicks IdleHelper::NowTicks() {
return helper_->NowTicks();
}
// static
bool IdleHelper::IsInIdlePeriod(IdlePeriodState state) {
return state != IdlePeriodState::kNotInIdlePeriod;
}
// static
bool IdleHelper::IsInLongIdlePeriod(IdlePeriodState state) {
return state == IdlePeriodState::kInLongIdlePeriod ||
state == IdlePeriodState::kInLongIdlePeriodWithMaxDeadline ||
state == IdlePeriodState::kInLongIdlePeriodPaused;
}
bool IdleHelper::CanExceedIdleDeadlineIfRequired() const {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"CanExceedIdleDeadlineIfRequired");
helper_->CheckOnValidThread();
return state_.idle_period_state() ==
IdlePeriodState::kInLongIdlePeriodWithMaxDeadline;
}
IdleHelper::IdlePeriodState IdleHelper::SchedulerIdlePeriodState() const {
return state_.idle_period_state();
}
IdleHelper::State::State(SchedulerHelper* helper,
Delegate* delegate,
const char* idle_period_tracing_name)
: helper_(helper),
delegate_(delegate),
idle_period_state_(IdlePeriodState::kNotInIdlePeriod),
idle_period_trace_event_started_(false),
running_idle_task_for_tracing_(false),
idle_period_tracing_name_(idle_period_tracing_name) {}
IdleHelper::State::~State() = default;
IdleHelper::IdlePeriodState IdleHelper::State::idle_period_state() const {
helper_->CheckOnValidThread();
return idle_period_state_;
}
base::TimeTicks IdleHelper::State::idle_period_deadline() const {
helper_->CheckOnValidThread();
return idle_period_deadline_;
}
void IdleHelper::State::UpdateState(IdlePeriodState new_state,
base::TimeTicks new_deadline,
base::TimeTicks optional_now) {
IdlePeriodState old_idle_period_state = idle_period_state_;
helper_->CheckOnValidThread();
if (new_state == idle_period_state_) {
DCHECK_EQ(new_deadline, idle_period_deadline_);
return;
}
bool is_tracing;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("renderer.scheduler", &is_tracing);
if (is_tracing) {
base::TimeTicks now(optional_now.is_null() ? helper_->NowTicks()
: optional_now);
TraceEventIdlePeriodStateChange(new_state, running_idle_task_for_tracing_,
new_deadline, now);
}
idle_period_state_ = new_state;
idle_period_deadline_ = new_deadline;
// Inform the delegate if we are starting or ending an idle period.
if (IsInIdlePeriod(new_state) && !IsInIdlePeriod(old_idle_period_state)) {
delegate_->OnIdlePeriodStarted();
} else if (!IsInIdlePeriod(new_state) &&
IsInIdlePeriod(old_idle_period_state)) {
delegate_->OnIdlePeriodEnded();
}
}
void IdleHelper::State::TraceIdleIdleTaskStart() {
helper_->CheckOnValidThread();
bool is_tracing;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("renderer.scheduler", &is_tracing);
if (is_tracing) {
TraceEventIdlePeriodStateChange(idle_period_state_, true,
idle_period_deadline_,
base::TimeTicks::Now());
}
}
void IdleHelper::State::TraceIdleIdleTaskEnd() {
helper_->CheckOnValidThread();
bool is_tracing;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("renderer.scheduler", &is_tracing);
if (is_tracing) {
TraceEventIdlePeriodStateChange(idle_period_state_, false,
idle_period_deadline_,
base::TimeTicks::Now());
}
}
void IdleHelper::State::TraceEventIdlePeriodStateChange(
IdlePeriodState new_state,
bool new_running_idle_task,
base::TimeTicks new_deadline,
base::TimeTicks now) {
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
"SetIdlePeriodState", "old_state",
IdleHelper::IdlePeriodStateToString(idle_period_state_),
"new_state", IdleHelper::IdlePeriodStateToString(new_state));
if (idle_period_trace_event_started_ && running_idle_task_for_tracing_ &&
!new_running_idle_task) {
running_idle_task_for_tracing_ = false;
if (!idle_period_deadline_.is_null() && now > idle_period_deadline_) {
TRACE_EVENT_ASYNC_STEP_INTO_WITH_TIMESTAMP0(
"renderer.scheduler", idle_period_tracing_name_, this,
"DeadlineOverrun",
std::max(idle_period_deadline_, last_idle_task_trace_time_));
}
}
if (IsInIdlePeriod(new_state)) {
if (!idle_period_trace_event_started_) {
idle_period_trace_event_started_ = true;
TRACE_EVENT_ASYNC_BEGIN1("renderer.scheduler", idle_period_tracing_name_,
this, "idle_period_length_ms",
(new_deadline - now).InMillisecondsF());
}
if (new_running_idle_task) {
last_idle_task_trace_time_ = now;
running_idle_task_for_tracing_ = true;
TRACE_EVENT_ASYNC_STEP_INTO0("renderer.scheduler",
idle_period_tracing_name_, this,
"RunningIdleTask");
} else if (new_state == IdlePeriodState::kInShortIdlePeriod) {
TRACE_EVENT_ASYNC_STEP_INTO0("renderer.scheduler",
idle_period_tracing_name_, this,
"ShortIdlePeriod");
} else if (IsInLongIdlePeriod(new_state) &&
new_state != IdlePeriodState::kInLongIdlePeriodPaused) {
TRACE_EVENT_ASYNC_STEP_INTO0("renderer.scheduler",
idle_period_tracing_name_, this,
"LongIdlePeriod");
} else if (new_state == IdlePeriodState::kInLongIdlePeriodPaused) {
TRACE_EVENT_ASYNC_STEP_INTO0("renderer.scheduler",
idle_period_tracing_name_, this,
"LongIdlePeriodPaused");
}
} else if (idle_period_trace_event_started_) {
idle_period_trace_event_started_ = false;
TRACE_EVENT_ASYNC_END0("renderer.scheduler", idle_period_tracing_name_,
this);
}
}
// static
const char* IdleHelper::IdlePeriodStateToString(
IdlePeriodState idle_period_state) {
switch (idle_period_state) {
case IdlePeriodState::kNotInIdlePeriod:
return "not_in_idle_period";
case IdlePeriodState::kInShortIdlePeriod:
return "in_short_idle_period";
case IdlePeriodState::kInLongIdlePeriod:
return "in_long_idle_period";
case IdlePeriodState::kInLongIdlePeriodWithMaxDeadline:
return "in_long_idle_period_with_max_deadline";
case IdlePeriodState::kInLongIdlePeriodPaused:
return "in_long_idle_period_paused";
default:
NOTREACHED();
return nullptr;
}
}
} // namespace scheduler
} // namespace blink