Index: content/renderer/scheduler/renderer_scheduler_impl.cc |
diff --git a/content/renderer/scheduler/renderer_scheduler_impl.cc b/content/renderer/scheduler/renderer_scheduler_impl.cc |
index 064eabfbff1d1efb100609bcc34edffe76d26339..dffd0b5d5b61951c0022d2bd3a8c301642c455cb 100644 |
--- a/content/renderer/scheduler/renderer_scheduler_impl.cc |
+++ b/content/renderer/scheduler/renderer_scheduler_impl.cc |
@@ -37,6 +37,8 @@ RendererSchedulerImpl::RendererSchedulerImpl( |
base::Unretained(this)), |
control_task_runner_), |
current_policy_(Policy::NORMAL), |
+ idle_period_state_(IdlePeriodState::NOT_IN_IDLE_PERIOD), |
+ long_idle_periods_enabled_(false), |
last_input_type_(blink::WebInputEvent::Undefined), |
input_stream_state_(InputStreamState::INACTIVE), |
policy_may_need_update_(&incoming_signals_lock_), |
@@ -46,6 +48,13 @@ RendererSchedulerImpl::RendererSchedulerImpl( |
weak_renderer_scheduler_ptr_); |
end_idle_period_closure_.Reset(base::Bind( |
&RendererSchedulerImpl::EndIdlePeriod, weak_renderer_scheduler_ptr_)); |
+ initiate_next_long_idle_period_closure_.Reset(base::Bind( |
+ &RendererSchedulerImpl::InitiateLongIdlePeriod, |
+ weak_renderer_scheduler_ptr_)); |
+ initiate_next_long_idle_period_after_wakeup_closure_.Reset(base::Bind( |
+ &RendererSchedulerImpl::InitiateLongIdlePeriodAfterWakeup, |
+ weak_renderer_scheduler_ptr_)); |
+ |
idle_task_runner_ = make_scoped_refptr(new SingleThreadIdleTaskRunner( |
task_queue_manager_->TaskRunnerForQueue(IDLE_TASK_QUEUE), |
control_task_after_wakeup_runner_, |
@@ -136,7 +145,9 @@ void RendererSchedulerImpl::DidCommitFrameToCompositor() { |
base::TimeTicks now(Now()); |
if (now < estimated_next_frame_begin_) { |
- StartIdlePeriod(); |
+ // TODO(rmcilroy): Consider reducing the idle period based on the runtime of |
+ // the next pending delayed tasks (as currently done in for long idle times) |
+ StartIdlePeriod(IdlePeriodState::IN_SHORT_IDLE_PERIOD); |
control_task_runner_->PostDelayedTask(FROM_HERE, |
end_idle_period_closure_.callback(), |
estimated_next_frame_begin_ - now); |
@@ -147,10 +158,14 @@ void RendererSchedulerImpl::BeginFrameNotExpectedSoon() { |
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
"RendererSchedulerImpl::BeginFrameNotExpectedSoon"); |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ if (!task_queue_manager_) |
+ return; |
+ |
// TODO(skyostil): Wire up real notification of input events processing |
// instead of this approximation. |
DidProcessInputEvent(base::TimeTicks()); |
- // TODO(rmcilroy): Implement long idle times. |
+ |
+ InitiateLongIdlePeriod(); |
} |
void RendererSchedulerImpl::DidReceiveInputEventOnCompositorThread( |
@@ -301,8 +316,11 @@ void RendererSchedulerImpl::UpdatePolicyLocked() { |
base::TimeDelta new_policy_duration; |
Policy new_policy = ComputeNewPolicy(now, &new_policy_duration); |
if (new_policy_duration > base::TimeDelta()) { |
+ current_policy_expiration_time_ = now + new_policy_duration; |
delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, |
now); |
+ } else { |
+ current_policy_expiration_time_ = base::TimeTicks(); |
} |
if (new_policy == current_policy_) |
@@ -354,67 +372,195 @@ RendererSchedulerImpl::Policy RendererSchedulerImpl::ComputeNewPolicy( |
if (input_stream_state_ == InputStreamState::INACTIVE) |
return new_policy; |
- base::TimeDelta new_priority_duration = |
- base::TimeDelta::FromMilliseconds(kPriorityEscalationAfterInputMillis); |
Policy input_priority_policy = |
input_stream_state_ == |
InputStreamState::ACTIVE_AND_AWAITING_TOUCHSTART_RESPONSE |
? Policy::TOUCHSTART_PRIORITY |
: Policy::COMPOSITOR_PRIORITY; |
+ base::TimeDelta time_left_in_policy = TimeLeftInInputEscalatedPolicy(now); |
+ if (time_left_in_policy > base::TimeDelta()) { |
+ new_policy = input_priority_policy; |
+ *new_policy_duration = time_left_in_policy; |
+ } else { |
+ // Reset |input_stream_state_| to ensure |
+ // DidReceiveInputEventOnCompositorThread will post an UpdatePolicy task |
+ // when it's next called. |
+ input_stream_state_ = InputStreamState::INACTIVE; |
+ } |
+ return new_policy; |
+} |
- // If the input event is still pending, go into input prioritized policy |
- // and check again later. |
+base::TimeDelta RendererSchedulerImpl::TimeLeftInInputEscalatedPolicy( |
+ base::TimeTicks now) const { |
+ DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ // TODO(rmcilroy): Change this to DCHECK_EQ when crbug.com/463869 is fixed. |
+ DCHECK(input_stream_state_ != InputStreamState::INACTIVE); |
+ incoming_signals_lock_.AssertAcquired(); |
+ |
+ base::TimeDelta escalated_priority_duration = |
+ base::TimeDelta::FromMilliseconds(kPriorityEscalationAfterInputMillis); |
+ base::TimeDelta time_left_in_policy; |
if (last_input_process_time_on_main_.is_null() && |
!task_queue_manager_->IsQueueEmpty(COMPOSITOR_TASK_QUEUE)) { |
- new_policy = input_priority_policy; |
- *new_policy_duration = new_priority_duration; |
+ // If the input event is still pending, go into input prioritized policy |
+ // and check again later. |
+ time_left_in_policy = escalated_priority_duration; |
} else { |
// Otherwise make sure the input prioritization policy ends on time. |
base::TimeTicks new_priority_end( |
std::max(last_input_receipt_time_on_compositor_, |
last_input_process_time_on_main_) + |
- new_priority_duration); |
- base::TimeDelta time_left_in_policy = new_priority_end - now; |
- |
- if (time_left_in_policy > base::TimeDelta()) { |
- new_policy = input_priority_policy; |
- *new_policy_duration = time_left_in_policy; |
- } else { |
- // Reset |input_stream_state_| to ensure |
- // DidReceiveInputEventOnCompositorThread will post an UpdatePolicy task |
- // when it's next called. |
- input_stream_state_ = InputStreamState::INACTIVE; |
- } |
+ escalated_priority_duration); |
+ time_left_in_policy = new_priority_end - now; |
+ } |
+ return time_left_in_policy; |
+} |
+ |
+RendererSchedulerImpl::IdlePeriodState |
+RendererSchedulerImpl::ComputeNewLongIdlePeriodState( |
+ const base::TimeTicks now, |
+ base::TimeDelta* next_long_idle_period_delay_out) { |
+ DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ |
+ MaybeUpdatePolicy(); |
+ if (SchedulerPolicy() == Policy::TOUCHSTART_PRIORITY) { |
+ // Don't start a long idle task in touch start priority, try again when |
+ // the policy is scheduled to end. |
+ *next_long_idle_period_delay_out = current_policy_expiration_time_ - now; |
+ return IdlePeriodState::NOT_IN_IDLE_PERIOD; |
+ } |
+ |
+ base::TimeTicks next_pending_delayed_task = |
+ task_queue_manager_->NextPendingDelayedTaskRunTime(); |
+ base::TimeDelta max_long_idle_period_duration = |
+ base::TimeDelta::FromMilliseconds(kMaximumIdlePeriodMillis); |
+ base::TimeDelta long_idle_period_duration; |
+ if (next_pending_delayed_task.is_null()) { |
+ long_idle_period_duration = max_long_idle_period_duration; |
+ } else { |
+ // 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); |
+ } |
+ |
+ if (long_idle_period_duration > base::TimeDelta()) { |
+ *next_long_idle_period_delay_out = long_idle_period_duration; |
+ return long_idle_period_duration == max_long_idle_period_duration |
+ ? IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE |
+ : IdlePeriodState::IN_LONG_IDLE_PERIOD; |
+ } else { |
+ // If we can't start the idle period yet then try again after wakeup. |
+ *next_long_idle_period_delay_out = base::TimeDelta::FromMilliseconds( |
+ kRetryInitiateLongIdlePeriodDelayMillis); |
+ return IdlePeriodState::NOT_IN_IDLE_PERIOD; |
} |
- return new_policy; |
} |
-void RendererSchedulerImpl::StartIdlePeriod() { |
+void RendererSchedulerImpl::InitiateLongIdlePeriod() { |
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
+ "InitiateLongIdlePeriod"); |
+ DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ |
+ // End any previous idle period. |
+ EndIdlePeriod(); |
+ |
+ base::TimeTicks now(Now()); |
+ base::TimeDelta next_long_idle_period_delay; |
+ IdlePeriodState new_idle_period_state = |
+ ComputeNewLongIdlePeriodState(now, &next_long_idle_period_delay); |
+ if (long_idle_periods_enabled_ && IsInIdlePeriod(new_idle_period_state)) { |
+ estimated_next_frame_begin_ = now + next_long_idle_period_delay; |
+ StartIdlePeriod(new_idle_period_state); |
+ } |
+ |
+ if (task_queue_manager_->IsQueueEmpty(IDLE_TASK_QUEUE)) { |
+ // If there are no current idle tasks then post the call to initiate the |
+ // next idle for execution after wakeup (at which point after-wakeup idle |
+ // tasks might be eligible to run or more idle tasks posted). |
+ control_task_after_wakeup_runner_->PostDelayedTask( |
+ FROM_HERE, |
+ initiate_next_long_idle_period_after_wakeup_closure_.callback(), |
+ next_long_idle_period_delay); |
+ } else { |
+ // Otherwise post on the normal control task queue. |
+ control_task_runner_->PostDelayedTask( |
+ FROM_HERE, |
+ initiate_next_long_idle_period_closure_.callback(), |
+ next_long_idle_period_delay); |
+ } |
+} |
+ |
+void RendererSchedulerImpl::InitiateLongIdlePeriodAfterWakeup() { |
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
+ "InitiateLongIdlePeriodAfterWakeup"); |
+ DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ |
+ if (IsInIdlePeriod(idle_period_state_)) { |
+ // Since we were asleep until now, end the async idle period trace event at |
+ // the time when it would have ended were we awake. |
+ TRACE_EVENT_ASYNC_END_WITH_TIMESTAMP0( |
+ "renderer.scheduler", "RendererSchedulerIdlePeriod", this, |
+ std::min(estimated_next_frame_begin_, Now()).ToInternalValue()); |
+ idle_period_state_ = IdlePeriodState::ENDING_LONG_IDLE_PERIOD; |
+ EndIdlePeriod(); |
+ } |
+ |
+ // Post a task to initiate the next long idle period rather than calling it |
+ // directly to allow all pending PostIdleTaskAfterWakeup tasks to get enqueued |
+ // on the idle task queue before the next idle period starts so they are |
+ // eligible to be run during the new idle period. |
+ control_task_runner_->PostTask( |
+ FROM_HERE, |
+ initiate_next_long_idle_period_closure_.callback()); |
+} |
+ |
+void RendererSchedulerImpl::StartIdlePeriod(IdlePeriodState new_state) { |
TRACE_EVENT_ASYNC_BEGIN0("renderer.scheduler", |
"RendererSchedulerIdlePeriod", this); |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ DCHECK(IsInIdlePeriod(new_state)); |
renderer_task_queue_selector_->EnableQueue( |
IDLE_TASK_QUEUE, RendererTaskQueueSelector::BEST_EFFORT_PRIORITY); |
task_queue_manager_->PumpQueue(IDLE_TASK_QUEUE); |
+ idle_period_state_ = new_state; |
} |
void RendererSchedulerImpl::EndIdlePeriod() { |
- bool is_tracing; |
- TRACE_EVENT_CATEGORY_GROUP_ENABLED("renderer.scheduler", &is_tracing); |
- if (is_tracing && !estimated_next_frame_begin_.is_null() && |
- base::TimeTicks::Now() > estimated_next_frame_begin_) { |
- TRACE_EVENT_ASYNC_STEP_INTO_WITH_TIMESTAMP0( |
- "renderer.scheduler", |
- "RendererSchedulerIdlePeriod", |
- this, |
- "DeadlineOverrun", |
- estimated_next_frame_begin_.ToInternalValue()); |
- } |
- TRACE_EVENT_ASYNC_END0("renderer.scheduler", |
- "RendererSchedulerIdlePeriod", this); |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ |
end_idle_period_closure_.Cancel(); |
+ initiate_next_long_idle_period_closure_.Cancel(); |
+ initiate_next_long_idle_period_after_wakeup_closure_.Cancel(); |
+ |
+ // If we weren't already within an idle period then early-out. |
+ if (!IsInIdlePeriod(idle_period_state_)) |
+ return; |
+ |
+ // If we are in the ENDING_LONG_IDLE_PERIOD state we have already logged the |
+ // trace event. |
+ if (idle_period_state_ != IdlePeriodState::ENDING_LONG_IDLE_PERIOD) { |
+ bool is_tracing; |
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED("renderer.scheduler", &is_tracing); |
+ if (is_tracing && !estimated_next_frame_begin_.is_null() && |
+ base::TimeTicks::Now() > estimated_next_frame_begin_) { |
+ TRACE_EVENT_ASYNC_STEP_INTO_WITH_TIMESTAMP0( |
+ "renderer.scheduler", |
+ "RendererSchedulerIdlePeriod", |
+ this, |
+ "DeadlineOverrun", |
+ estimated_next_frame_begin_.ToInternalValue()); |
+ } |
+ TRACE_EVENT_ASYNC_END0("renderer.scheduler", |
+ "RendererSchedulerIdlePeriod", this); |
+ } |
+ |
renderer_task_queue_selector_->DisableQueue(IDLE_TASK_QUEUE); |
+ idle_period_state_ = IdlePeriodState::NOT_IN_IDLE_PERIOD; |
+} |
+ |
+// static |
+bool RendererSchedulerImpl::IsInIdlePeriod(IdlePeriodState state) { |
+ return state != IdlePeriodState::NOT_IN_IDLE_PERIOD; |
} |
void RendererSchedulerImpl::SetTimeSourceForTesting( |
@@ -429,6 +575,12 @@ void RendererSchedulerImpl::SetWorkBatchSizeForTesting(size_t work_batch_size) { |
task_queue_manager_->SetWorkBatchSize(work_batch_size); |
} |
+void RendererSchedulerImpl::SetLongIdlePeriodsEnabledForTesting( |
+ bool long_idle_periods_enabled) { |
+ DCHECK(main_thread_checker_.CalledOnValidThread()); |
+ long_idle_periods_enabled_ = long_idle_periods_enabled; |
+} |
+ |
base::TimeTicks RendererSchedulerImpl::Now() const { |
return UNLIKELY(time_source_) ? time_source_->Now() : base::TimeTicks::Now(); |
} |
@@ -502,6 +654,25 @@ const char* RendererSchedulerImpl::InputStreamStateToString( |
} |
} |
+const char* RendererSchedulerImpl::IdlePeriodStateToString( |
+ IdlePeriodState idle_period_state) { |
+ switch (idle_period_state) { |
+ case IdlePeriodState::NOT_IN_IDLE_PERIOD: |
+ return "not_in_idle_period"; |
+ case IdlePeriodState::IN_SHORT_IDLE_PERIOD: |
+ return "in_short_idle_period"; |
+ case IdlePeriodState::IN_LONG_IDLE_PERIOD: |
+ return "in_long_idle_period"; |
+ case IdlePeriodState::IN_LONG_IDLE_PERIOD_WITH_MAX_DEADLINE: |
+ return "in_long_idle_period_with_max_deadline"; |
+ case IdlePeriodState::ENDING_LONG_IDLE_PERIOD: |
+ return "ending_long_idle_period"; |
+ default: |
+ NOTREACHED(); |
+ return nullptr; |
+ } |
+} |
+ |
scoped_refptr<base::trace_event::ConvertableToTraceFormat> |
RendererSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const { |
DCHECK(main_thread_checker_.CalledOnValidThread()); |
@@ -513,6 +684,8 @@ RendererSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const { |
new base::trace_event::TracedValue(); |
state->SetString("current_policy", PolicyToString(current_policy_)); |
+ state->SetString("idle_period_state", |
+ IdlePeriodStateToString(idle_period_state_)); |
state->SetString("input_stream_state", |
InputStreamStateToString(input_stream_state_)); |
state->SetDouble("now", (optional_now - base::TimeTicks()).InMillisecondsF()); |