Index: components/scheduler/renderer/renderer_scheduler_impl.cc |
diff --git a/components/scheduler/renderer/renderer_scheduler_impl.cc b/components/scheduler/renderer/renderer_scheduler_impl.cc |
index f05fc4994421f2659ea92e318bd8790098ae1f19..e58e62cbe7cc5d9b598cfa4ae59b03c91b51df50 100644 |
--- a/components/scheduler/renderer/renderer_scheduler_impl.cc |
+++ b/components/scheduler/renderer/renderer_scheduler_impl.cc |
@@ -15,8 +15,10 @@ |
namespace scheduler { |
namespace { |
-const int kTimerTaskEstimationSampleCount = 4 * 60; |
-const double kTimerTaskEstimationPercentile = 80; |
+const int kLoadingTaskEstimationSampleCount = 200; |
+const double kLoadingTaskEstimationPercentile = 90; |
+const int kTimerTaskEstimationSampleCount = 200; |
+const double kTimerTaskEstimationPercentile = 90; |
const int kShortIdlePeriodDurationSampleCount = 10; |
const double kShortIdlePeriodDurationPercentile = 20; |
} |
@@ -58,12 +60,19 @@ RendererSchedulerImpl::RendererSchedulerImpl( |
base::Bind(&RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded, |
weak_factory_.GetWeakPtr())); |
+ loading_task_runner_->AddTaskObserver( |
+ &MainThreadOnly().loading_task_cost_estimator); |
+ |
timer_task_runner_->AddTaskObserver( |
- &MainThreadOnly().timer_task_cost_estimator_); |
+ &MainThreadOnly().timer_task_cost_estimator); |
TRACE_EVENT_OBJECT_CREATED_WITH_ID( |
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", |
this); |
+ |
+ // Make sure that we don't initially assume there is no idle time. |
+ MainThreadOnly().short_idle_period_duration.InsertSample( |
+ cc::BeginFrameArgs::DefaultInterval()); |
} |
RendererSchedulerImpl::~RendererSchedulerImpl() { |
@@ -71,44 +80,55 @@ RendererSchedulerImpl::~RendererSchedulerImpl() { |
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", |
this); |
timer_task_runner_->RemoveTaskObserver( |
- &MainThreadOnly().timer_task_cost_estimator_); |
+ &MainThreadOnly().timer_task_cost_estimator); |
+ loading_task_runner_->RemoveTaskObserver( |
+ &MainThreadOnly().loading_task_cost_estimator); |
// Ensure the renderer scheduler was shut down explicitly, because otherwise |
// we could end up having stale pointers to the Blink heap which has been |
// terminated by this point. |
- DCHECK(MainThreadOnly().was_shutdown_); |
+ DCHECK(MainThreadOnly().was_shutdown); |
} |
+RendererSchedulerImpl::Policy::Policy() |
+ : compositor_queue_priority(TaskQueue::NORMAL_PRIORITY), |
+ loading_queue_priority(TaskQueue::NORMAL_PRIORITY), |
+ timer_queue_priority(TaskQueue::NORMAL_PRIORITY), |
+ default_queue_priority(TaskQueue::NORMAL_PRIORITY) {} |
+ |
RendererSchedulerImpl::MainThreadOnly::MainThreadOnly() |
- : timer_task_cost_estimator_(kTimerTaskEstimationSampleCount, |
- kTimerTaskEstimationPercentile), |
- short_idle_period_duration_(kShortIdlePeriodDurationSampleCount), |
- current_policy_(Policy::NORMAL), |
- timer_queue_suspend_count_(0), |
- renderer_hidden_(false), |
- renderer_backgrounded_(false), |
- timer_queue_suspension_when_backgrounded_enabled_(false), |
- timer_queue_suspended_when_backgrounded_(false), |
- was_shutdown_(false) {} |
+ : loading_task_cost_estimator(kLoadingTaskEstimationSampleCount, |
+ kLoadingTaskEstimationPercentile), |
+ timer_task_cost_estimator(kTimerTaskEstimationSampleCount, |
+ kTimerTaskEstimationPercentile), |
+ short_idle_period_duration(kShortIdlePeriodDurationSampleCount), |
+ current_use_case(UseCase::NONE), |
+ timer_queue_suspend_count(0), |
+ renderer_hidden(false), |
+ renderer_backgrounded(false), |
+ timer_queue_suspension_when_backgrounded_enabled(false), |
+ timer_queue_suspended_when_backgrounded(false), |
+ was_shutdown(false), |
+ loading_tasks_seem_expensive(false), |
+ timer_tasks_seem_expensive(false), |
+ touchstart_expected_soon(false), |
+ have_seen_a_begin_main_frame(false) {} |
RendererSchedulerImpl::MainThreadOnly::~MainThreadOnly() {} |
RendererSchedulerImpl::AnyThread::AnyThread() |
- : pending_main_thread_input_event_count_(0), |
- awaiting_touch_start_response_(false), |
- in_idle_period_(false), |
- begin_main_frame_on_critical_path_(false), |
- timer_tasks_seem_expensive_(false) {} |
+ : awaiting_touch_start_response(false), |
+ in_idle_period(false), |
+ begin_main_frame_on_critical_path(false) {} |
RendererSchedulerImpl::CompositorThreadOnly::CompositorThreadOnly() |
- : last_input_type_(blink::WebInputEvent::Undefined) { |
-} |
+ : last_input_type(blink::WebInputEvent::Undefined) {} |
RendererSchedulerImpl::CompositorThreadOnly::~CompositorThreadOnly() { |
} |
void RendererSchedulerImpl::Shutdown() { |
helper_.Shutdown(); |
- MainThreadOnly().was_shutdown_ = true; |
+ MainThreadOnly().was_shutdown = true; |
} |
scoped_refptr<TaskQueue> RendererSchedulerImpl::DefaultTaskRunner() { |
@@ -159,11 +179,11 @@ void RendererSchedulerImpl::WillBeginFrame(const cc::BeginFrameArgs& args) { |
return; |
EndIdlePeriod(); |
- MainThreadOnly().estimated_next_frame_begin_ = |
- args.frame_time + args.interval; |
+ MainThreadOnly().estimated_next_frame_begin = args.frame_time + args.interval; |
+ MainThreadOnly().have_seen_a_begin_main_frame = true; |
{ |
base::AutoLock lock(any_thread_lock_); |
- AnyThread().begin_main_frame_on_critical_path_ = args.on_critical_path; |
+ AnyThread().begin_main_frame_on_critical_path = args.on_critical_path; |
} |
} |
@@ -175,18 +195,22 @@ void RendererSchedulerImpl::DidCommitFrameToCompositor() { |
return; |
base::TimeTicks now(helper_.Now()); |
- if (now < MainThreadOnly().estimated_next_frame_begin_) { |
+ if (now < MainThreadOnly().estimated_next_frame_begin) { |
// 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) |
idle_helper_.StartIdlePeriod( |
IdleHelper::IdlePeriodState::IN_SHORT_IDLE_PERIOD, now, |
- MainThreadOnly().estimated_next_frame_begin_); |
- MainThreadOnly().short_idle_period_duration_.InsertSample( |
- MainThreadOnly().estimated_next_frame_begin_ - now); |
- MainThreadOnly().expected_short_idle_period_duration_ = |
- MainThreadOnly().short_idle_period_duration_.Percentile( |
- kShortIdlePeriodDurationPercentile); |
+ MainThreadOnly().estimated_next_frame_begin); |
+ MainThreadOnly().short_idle_period_duration.InsertSample( |
+ MainThreadOnly().estimated_next_frame_begin - now); |
+ } else { |
+ // There was no idle time :( |
+ MainThreadOnly().short_idle_period_duration.InsertSample(base::TimeDelta()); |
} |
+ |
+ MainThreadOnly().expected_short_idle_period_duration = |
+ MainThreadOnly().short_idle_period_duration.Percentile( |
+ kShortIdlePeriodDurationPercentile); |
} |
void RendererSchedulerImpl::BeginFrameNotExpectedSoon() { |
@@ -203,7 +227,7 @@ void RendererSchedulerImpl::OnRendererHidden() { |
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
"RendererSchedulerImpl::OnRendererHidden"); |
helper_.CheckOnValidThread(); |
- if (helper_.IsShutdown() || MainThreadOnly().renderer_hidden_) |
+ if (helper_.IsShutdown() || MainThreadOnly().renderer_hidden) |
return; |
idle_helper_.EnableLongIdlePeriod(); |
@@ -215,7 +239,7 @@ void RendererSchedulerImpl::OnRendererHidden() { |
control_task_runner_->PostDelayedTask( |
FROM_HERE, end_renderer_hidden_idle_period_closure_.callback(), |
end_idle_when_hidden_delay); |
- MainThreadOnly().renderer_hidden_ = true; |
+ MainThreadOnly().renderer_hidden = true; |
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( |
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", |
@@ -226,11 +250,11 @@ void RendererSchedulerImpl::OnRendererVisible() { |
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
"RendererSchedulerImpl::OnRendererVisible"); |
helper_.CheckOnValidThread(); |
- if (helper_.IsShutdown() || !MainThreadOnly().renderer_hidden_) |
+ if (helper_.IsShutdown() || !MainThreadOnly().renderer_hidden) |
return; |
end_renderer_hidden_idle_period_closure_.Cancel(); |
- MainThreadOnly().renderer_hidden_ = false; |
+ MainThreadOnly().renderer_hidden = false; |
EndIdlePeriod(); |
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( |
@@ -242,11 +266,11 @@ void RendererSchedulerImpl::OnRendererBackgrounded() { |
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
"RendererSchedulerImpl::OnRendererBackgrounded"); |
helper_.CheckOnValidThread(); |
- if (helper_.IsShutdown() || MainThreadOnly().renderer_backgrounded_) |
+ if (helper_.IsShutdown() || MainThreadOnly().renderer_backgrounded) |
return; |
- MainThreadOnly().renderer_backgrounded_ = true; |
- if (!MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled_) |
+ MainThreadOnly().renderer_backgrounded = true; |
+ if (!MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled) |
return; |
suspend_timers_when_backgrounded_closure_.Cancel(); |
@@ -262,10 +286,10 @@ void RendererSchedulerImpl::OnRendererForegrounded() { |
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
"RendererSchedulerImpl::OnRendererForegrounded"); |
helper_.CheckOnValidThread(); |
- if (helper_.IsShutdown() || !MainThreadOnly().renderer_backgrounded_) |
+ if (helper_.IsShutdown() || !MainThreadOnly().renderer_backgrounded) |
return; |
- MainThreadOnly().renderer_backgrounded_ = false; |
+ MainThreadOnly().renderer_backgrounded = false; |
suspend_timers_when_backgrounded_closure_.Cancel(); |
ResumeTimerQueueWhenForegrounded(); |
} |
@@ -322,14 +346,27 @@ void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread( |
InputEventState input_event_state) { |
base::AutoLock lock(any_thread_lock_); |
base::TimeTicks now = helper_.Now(); |
- bool was_in_compositor_priority = InputSignalsSuggestCompositorPriority(now); |
+ |
+ // TODO(alexclarke): Move WebInputEventTraits where we can access it from here |
+ // and record the name rather than the integer representation. |
+ TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
+ "RendererSchedulerImpl::UpdateForInputEventOnCompositorThread", |
+ "type", static_cast<int>(type), "input_event_state", |
+ InputEventStateToString(input_event_state)); |
+ |
+ bool gesture_already_in_progress = InputSignalsSuggestGestureInProgress(now); |
bool was_awaiting_touch_start_response = |
- AnyThread().awaiting_touch_start_response_; |
+ AnyThread().awaiting_touch_start_response; |
+ |
+ AnyThread().user_model.DidStartProcessingInputEvent(type, now); |
+ |
+ if (input_event_state == InputEventState::EVENT_CONSUMED_BY_COMPOSITOR) |
+ AnyThread().user_model.DidFinishProcessingInputEvent(now); |
if (type) { |
switch (type) { |
case blink::WebInputEvent::TouchStart: |
- AnyThread().awaiting_touch_start_response_ = true; |
+ AnyThread().awaiting_touch_start_response = true; |
break; |
case blink::WebInputEvent::TouchMove: |
@@ -338,10 +375,10 @@ void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread( |
// response prioritization is no longer necessary. Otherwise, the |
// initial touchmove should preserve the touchstart response pending |
// state. |
- if (AnyThread().awaiting_touch_start_response_ && |
- CompositorThreadOnly().last_input_type_ == |
+ if (AnyThread().awaiting_touch_start_response && |
+ CompositorThreadOnly().last_input_type == |
blink::WebInputEvent::TouchMove) { |
- AnyThread().awaiting_touch_start_response_ = false; |
+ AnyThread().awaiting_touch_start_response = false; |
} |
break; |
@@ -355,22 +392,18 @@ void RendererSchedulerImpl::UpdateForInputEventOnCompositorThread( |
break; |
default: |
- AnyThread().awaiting_touch_start_response_ = false; |
+ AnyThread().awaiting_touch_start_response = false; |
break; |
} |
} |
- // Avoid unnecessary policy updates, while in compositor priority. |
- if (!was_in_compositor_priority || |
+ // Avoid unnecessary policy updates, while a gesture is already in progress. |
+ if (!gesture_already_in_progress || |
was_awaiting_touch_start_response != |
- AnyThread().awaiting_touch_start_response_) { |
+ AnyThread().awaiting_touch_start_response) { |
EnsureUrgentPolicyUpdatePostedOnMainThread(FROM_HERE); |
} |
- AnyThread().last_input_signal_time_ = now; |
- CompositorThreadOnly().last_input_type_ = type; |
- |
- if (input_event_state == InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD) |
- AnyThread().pending_main_thread_input_event_count_++; |
+ CompositorThreadOnly().last_input_type = type; |
} |
void RendererSchedulerImpl::DidHandleInputEventOnMainThread( |
@@ -380,9 +413,7 @@ void RendererSchedulerImpl::DidHandleInputEventOnMainThread( |
helper_.CheckOnValidThread(); |
if (ShouldPrioritizeInputEvent(web_input_event)) { |
base::AutoLock lock(any_thread_lock_); |
- AnyThread().last_input_signal_time_ = helper_.Now(); |
- if (AnyThread().pending_main_thread_input_event_count_ > 0) |
- AnyThread().pending_main_thread_input_event_count_--; |
+ AnyThread().user_model.DidFinishProcessingInputEvent(helper_.Now()); |
} |
} |
@@ -392,12 +423,12 @@ bool RendererSchedulerImpl::IsHighPriorityWorkAnticipated() { |
return false; |
MaybeUpdatePolicy(); |
- // The touchstart and compositor policies indicate a strong likelihood of |
- // high-priority work in the near future. |
- return MainThreadOnly().current_policy_ == Policy::COMPOSITOR_PRIORITY || |
- MainThreadOnly().current_policy_ == |
- Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY || |
- MainThreadOnly().current_policy_ == Policy::TOUCHSTART_PRIORITY; |
+ // The touchstart and main-thread gesture use cases indicate a strong |
+ // likelihood of high-priority work in the near future. |
+ UseCase use_case = MainThreadOnly().current_use_case; |
+ return MainThreadOnly().touchstart_expected_soon || |
+ use_case == UseCase::TOUCHSTART || |
+ use_case == UseCase::MAIN_THREAD_GESTURE; |
} |
bool RendererSchedulerImpl::ShouldYieldForHighPriorityWork() { |
@@ -406,25 +437,26 @@ bool RendererSchedulerImpl::ShouldYieldForHighPriorityWork() { |
return false; |
MaybeUpdatePolicy(); |
- // We only yield if we are in the compositor priority and there is compositor |
- // work outstanding, or if we are in the touchstart response priority. |
- // Note: even though the control queue is higher priority we don't yield for |
- // it since these tasks are not user-provided work and they are only intended |
- // to run before the next task, not interrupt the tasks. |
- switch (MainThreadOnly().current_policy_) { |
- case Policy::NORMAL: |
- return false; |
- |
- case Policy::COMPOSITOR_PRIORITY: |
- return !compositor_task_runner_->IsQueueEmpty(); |
- |
- case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: |
- return !compositor_task_runner_->IsQueueEmpty(); |
- |
- case Policy::TOUCHSTART_PRIORITY: |
+ // We only yield if there's a urgent task to be run now, or we are expecting |
+ // one soon (touch start). |
+ // Note: even though the control queue has the highest priority we don't yield |
+ // for it since these tasks are not user-provided work and they are only |
+ // intended to run before the next task, not interrupt the tasks. |
+ switch (MainThreadOnly().current_use_case) { |
+ case UseCase::NONE: |
+ return MainThreadOnly().touchstart_expected_soon; |
+ |
+ case UseCase::COMPOSITOR_GESTURE: |
+ return MainThreadOnly().touchstart_expected_soon; |
+ |
+ case UseCase::MAIN_THREAD_GESTURE: |
+ return !compositor_task_runner_->IsQueueEmpty() || |
+ MainThreadOnly().touchstart_expected_soon; |
+ |
+ case UseCase::TOUCHSTART: |
return true; |
- case Policy::LOADING_PRIORITY: |
+ case UseCase::LOADING: |
return false; |
default: |
@@ -475,92 +507,142 @@ void RendererSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) { |
base::TimeTicks now = helper_.Now(); |
policy_may_need_update_.SetWhileLocked(false); |
- AnyThread().timer_tasks_seem_expensive_ = |
- MainThreadOnly().expected_short_idle_period_duration_ > |
- base::TimeDelta() && |
- MainThreadOnly().timer_task_cost_estimator_.expected_task_duration() > |
- MainThreadOnly().expected_short_idle_period_duration_; |
+ base::TimeDelta expected_use_case_duration; |
+ UseCase use_case = ComputeCurrentUseCase(now, &expected_use_case_duration); |
+ MainThreadOnly().current_use_case = use_case; |
+ |
+ // TODO(alexclarke): We should wire up a signal from blink to let us know if |
+ // there are any touch handlers registerd or not, and only call |
+ // TouchStartExpectedSoon if there is at least one. NOTE a TouchStart will |
+ // only actually get sent if there is a touch handler. |
+ base::TimeDelta touchstart_expected_flag_valid_for_duration; |
+ bool touchstart_expected_soon = AnyThread().user_model.IsGestureExpectedSoon( |
+ use_case, now, &touchstart_expected_flag_valid_for_duration); |
+ MainThreadOnly().touchstart_expected_soon = touchstart_expected_soon; |
+ |
+ bool loading_tasks_seem_expensive = |
+ MainThreadOnly().loading_task_cost_estimator.expected_task_duration() > |
+ MainThreadOnly().expected_short_idle_period_duration; |
+ MainThreadOnly().loading_tasks_seem_expensive = loading_tasks_seem_expensive; |
+ |
+ bool timer_tasks_seem_expensive = |
+ MainThreadOnly().timer_task_cost_estimator.expected_task_duration() > |
+ MainThreadOnly().expected_short_idle_period_duration; |
+ MainThreadOnly().timer_tasks_seem_expensive = timer_tasks_seem_expensive; |
+ |
+ // The |new_policy_duration| is the minimum of |expected_use_case_duration| |
+ // and |touchstart_expected_flag_valid_for_duration| unless one is zero in |
+ // which case we choose the other. |
+ base::TimeDelta new_policy_duration = expected_use_case_duration; |
+ if (new_policy_duration == base::TimeDelta() || |
+ (touchstart_expected_flag_valid_for_duration > base::TimeDelta() && |
+ new_policy_duration > touchstart_expected_flag_valid_for_duration)) { |
+ new_policy_duration = touchstart_expected_flag_valid_for_duration; |
+ } |
- base::TimeDelta new_policy_duration; |
- Policy new_policy = ComputeNewPolicy(now, &new_policy_duration); |
if (new_policy_duration > base::TimeDelta()) { |
- MainThreadOnly().current_policy_expiration_time_ = |
- now + new_policy_duration; |
+ MainThreadOnly().current_policy_expiration_time = now + new_policy_duration; |
delayed_update_policy_runner_.SetDeadline(FROM_HERE, new_policy_duration, |
now); |
} else { |
- MainThreadOnly().current_policy_expiration_time_ = base::TimeTicks(); |
+ MainThreadOnly().current_policy_expiration_time = base::TimeTicks(); |
} |
- if (update_type == UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED && |
- new_policy == MainThreadOnly().current_policy_) |
- return; |
- |
- TaskQueue::QueuePriority compositor_queue_priority = |
- TaskQueue::NORMAL_PRIORITY; |
- TaskQueue::QueuePriority loading_queue_priority = TaskQueue::NORMAL_PRIORITY; |
- TaskQueue::QueuePriority timer_queue_priority = TaskQueue::NORMAL_PRIORITY; |
- |
- if (MainThreadOnly().timer_queue_suspend_count_ != 0 || |
- MainThreadOnly().timer_queue_suspended_when_backgrounded_) { |
- timer_queue_priority = TaskQueue::DISABLED_PRIORITY; |
- } |
- |
- switch (new_policy) { |
- case Policy::COMPOSITOR_PRIORITY: |
- compositor_queue_priority = TaskQueue::HIGH_PRIORITY; |
+ Policy new_policy; |
+ bool block_expensive_tasks = false; |
+ switch (use_case) { |
+ case UseCase::COMPOSITOR_GESTURE: |
+ if (touchstart_expected_soon) { |
+ block_expensive_tasks = true; |
+ } else { |
+ // What we really want to do is priorize loading tasks, but that doesn't |
+ // seem to be safe. Instead we do that by proxy by deprioritizing |
+ // compositor tasks. This should be safe since we've already gone to the |
+ // pain of fixing ordering issues with them. |
+ new_policy.compositor_queue_priority = TaskQueue::BEST_EFFORT_PRIORITY; |
+ } |
break; |
- case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: |
- compositor_queue_priority = TaskQueue::HIGH_PRIORITY; |
- loading_queue_priority = TaskQueue::DISABLED_PRIORITY; |
- timer_queue_priority = TaskQueue::DISABLED_PRIORITY; |
+ |
+ case UseCase::MAIN_THREAD_GESTURE: |
+ new_policy.compositor_queue_priority = TaskQueue::HIGH_PRIORITY; |
+ block_expensive_tasks = true; |
break; |
- case Policy::TOUCHSTART_PRIORITY: |
- compositor_queue_priority = TaskQueue::HIGH_PRIORITY; |
- loading_queue_priority = TaskQueue::DISABLED_PRIORITY; |
- timer_queue_priority = TaskQueue::DISABLED_PRIORITY; |
+ |
+ case UseCase::TOUCHSTART: |
+ new_policy.compositor_queue_priority = TaskQueue::HIGH_PRIORITY; |
+ new_policy.loading_queue_priority = TaskQueue::DISABLED_PRIORITY; |
+ new_policy.timer_queue_priority = TaskQueue::DISABLED_PRIORITY; |
+ block_expensive_tasks = true; // NOTE this is a nop due to the above. |
break; |
- case Policy::NORMAL: |
+ |
+ case UseCase::NONE: |
+ if (touchstart_expected_soon) |
+ block_expensive_tasks = true; |
break; |
- case Policy::LOADING_PRIORITY: |
- // We prioritize loading tasks by deprioritizing compositing and timers. |
- compositor_queue_priority = TaskQueue::BEST_EFFORT_PRIORITY; |
- timer_queue_priority = TaskQueue::BEST_EFFORT_PRIORITY; |
- // TODO(alexclarke): See if we can safely mark the loading task queue as |
- // high priority. |
+ |
+ case UseCase::LOADING: |
+ new_policy.loading_queue_priority = TaskQueue::HIGH_PRIORITY; |
+ new_policy.default_queue_priority = TaskQueue::HIGH_PRIORITY; |
break; |
+ |
default: |
NOTREACHED(); |
} |
- compositor_task_runner_->SetQueuePriority(compositor_queue_priority); |
- loading_task_runner_->SetQueuePriority(loading_queue_priority); |
- timer_task_runner_->SetQueuePriority(timer_queue_priority); |
+ // Don't block expensive tasks unless we have actually seen something. |
+ if (!MainThreadOnly().have_seen_a_begin_main_frame) |
+ block_expensive_tasks = false; |
- DCHECK(compositor_task_runner_->IsQueueEnabled()); |
- if (new_policy != Policy::TOUCHSTART_PRIORITY && |
- new_policy != Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY) { |
- DCHECK(loading_task_runner_->IsQueueEnabled()); |
+ if (block_expensive_tasks && loading_tasks_seem_expensive) |
+ new_policy.loading_queue_priority = TaskQueue::DISABLED_PRIORITY; |
+ |
+ if ((block_expensive_tasks && timer_tasks_seem_expensive) || |
+ MainThreadOnly().timer_queue_suspend_count != 0 || |
+ MainThreadOnly().timer_queue_suspended_when_backgrounded) { |
+ new_policy.timer_queue_priority = TaskQueue::DISABLED_PRIORITY; |
} |
- MainThreadOnly().current_policy_ = new_policy; |
+ // Tracing is done before the early out check, because it's quite possible we |
+ // will otherwise miss this information in traces. |
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( |
TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "RendererScheduler", |
this, AsValueLocked(now)); |
+ TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "use_case", |
+ use_case); |
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
- "RendererScheduler.policy", MainThreadOnly().current_policy_); |
+ "RendererScheduler.loading_tasks_seem_expensive", |
+ MainThreadOnly().loading_tasks_seem_expensive); |
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
"RendererScheduler.timer_tasks_seem_expensive", |
- AnyThread().timer_tasks_seem_expensive_); |
+ MainThreadOnly().timer_tasks_seem_expensive); |
+ |
+ if (update_type == UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED && |
+ new_policy == MainThreadOnly().current_policy) { |
+ return; |
+ } |
+ |
+ compositor_task_runner_->SetQueuePriority( |
+ new_policy.compositor_queue_priority); |
+ loading_task_runner_->SetQueuePriority(new_policy.loading_queue_priority); |
+ timer_task_runner_->SetQueuePriority(new_policy.timer_queue_priority); |
+ |
+ // TODO(alexclarke): We shouldn't have to prioritize the default queue, but it |
+ // appears to be necessary since the order of loading tasks and IPCs (which |
+ // are mostly dispatched on the default queue) need to be preserved. |
+ helper_.DefaultTaskRunner()->SetQueuePriority( |
+ new_policy.default_queue_priority); |
+ |
+ DCHECK(compositor_task_runner_->IsQueueEnabled()); |
+ MainThreadOnly().current_policy = new_policy; |
} |
-bool RendererSchedulerImpl::InputSignalsSuggestCompositorPriority( |
+bool RendererSchedulerImpl::InputSignalsSuggestGestureInProgress( |
base::TimeTicks now) const { |
base::TimeDelta unused_policy_duration; |
- switch (ComputeNewPolicy(now, &unused_policy_duration)) { |
- case Policy::TOUCHSTART_PRIORITY: |
- case Policy::COMPOSITOR_PRIORITY: |
- case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: |
+ switch (ComputeCurrentUseCase(now, &unused_policy_duration)) { |
+ case UseCase::COMPOSITOR_GESTURE: |
+ case UseCase::MAIN_THREAD_GESTURE: |
+ case UseCase::TOUCHSTART: |
return true; |
default: |
@@ -569,52 +651,35 @@ bool RendererSchedulerImpl::InputSignalsSuggestCompositorPriority( |
return false; |
} |
-RendererSchedulerImpl::Policy RendererSchedulerImpl::ComputeNewPolicy( |
+RendererSchedulerImpl::UseCase RendererSchedulerImpl::ComputeCurrentUseCase( |
base::TimeTicks now, |
- base::TimeDelta* new_policy_duration) const { |
+ base::TimeDelta* expected_use_case_duration) const { |
any_thread_lock_.AssertAcquired(); |
// Above all else we want to be responsive to user input. |
- *new_policy_duration = TimeLeftInInputEscalatedPolicy(now); |
- if (*new_policy_duration > base::TimeDelta()) { |
- if (AnyThread().awaiting_touch_start_response_) |
- return Policy::TOUCHSTART_PRIORITY; |
- // If BeginMainFrame is on the critical path, we want to try and prevent |
- // timers and loading tasks from running if we think they might be |
- // expensive. |
+ *expected_use_case_duration = |
+ AnyThread().user_model.TimeLeftInUserGesture(now); |
+ if (*expected_use_case_duration > base::TimeDelta()) { |
+ // Has scrolling been fully established? |
+ if (AnyThread().awaiting_touch_start_response) { |
+ // No, so arrange for compositor tasks to be run at the highest priority. |
+ return UseCase::TOUCHSTART; |
+ } |
+ // Yes scrolling has been established. If BeginMainFrame is on the critical |
+ // path, compositor tasks need to be prioritized, otherwise now might be a |
+ // good time to run potentially expensive work. |
// TODO(skyostil): Consider removing in_idle_period_ and |
// HadAnIdlePeriodRecently() unless we need them here. |
- if (AnyThread().timer_tasks_seem_expensive_ && |
- AnyThread().begin_main_frame_on_critical_path_) { |
- return Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY; |
+ if (AnyThread().begin_main_frame_on_critical_path) { |
+ return UseCase::MAIN_THREAD_GESTURE; |
+ } else { |
+ return UseCase::COMPOSITOR_GESTURE; |
} |
- return Policy::COMPOSITOR_PRIORITY; |
} |
- if (AnyThread().rails_loading_priority_deadline_ > now) { |
- *new_policy_duration = AnyThread().rails_loading_priority_deadline_ - now; |
- return Policy::LOADING_PRIORITY; |
- } |
+ // TODO(alexclarke): return UseCase::LOADING if signals suggest the system is |
+ // in the initial 1s of RAIL loading. |
- return Policy::NORMAL; |
-} |
- |
-base::TimeDelta RendererSchedulerImpl::TimeLeftInInputEscalatedPolicy( |
- base::TimeTicks now) const { |
- any_thread_lock_.AssertAcquired(); |
- |
- base::TimeDelta escalated_priority_duration = |
- base::TimeDelta::FromMilliseconds(kPriorityEscalationAfterInputMillis); |
- |
- // If the input event is still pending, go into input prioritized policy |
- // and check again later. |
- if (AnyThread().pending_main_thread_input_event_count_ > 0) |
- return escalated_priority_duration; |
- if (AnyThread().last_input_signal_time_.is_null() || |
- AnyThread().last_input_signal_time_ + escalated_priority_duration < now) { |
- return base::TimeDelta(); |
- } |
- return AnyThread().last_input_signal_time_ + escalated_priority_duration - |
- now; |
+ return UseCase::NONE; |
} |
bool RendererSchedulerImpl::CanEnterLongIdlePeriod( |
@@ -623,11 +688,11 @@ bool RendererSchedulerImpl::CanEnterLongIdlePeriod( |
helper_.CheckOnValidThread(); |
MaybeUpdatePolicy(); |
- if (MainThreadOnly().current_policy_ == Policy::TOUCHSTART_PRIORITY) { |
+ if (MainThreadOnly().current_use_case == UseCase::TOUCHSTART) { |
// 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 = |
- MainThreadOnly().current_policy_expiration_time_ - now; |
+ MainThreadOnly().current_policy_expiration_time - now; |
return false; |
} |
return true; |
@@ -638,40 +703,21 @@ SchedulerHelper* RendererSchedulerImpl::GetSchedulerHelperForTesting() { |
} |
void RendererSchedulerImpl::SuspendTimerQueue() { |
- MainThreadOnly().timer_queue_suspend_count_++; |
+ MainThreadOnly().timer_queue_suspend_count++; |
ForceUpdatePolicy(); |
DCHECK(!timer_task_runner_->IsQueueEnabled()); |
} |
void RendererSchedulerImpl::ResumeTimerQueue() { |
- MainThreadOnly().timer_queue_suspend_count_--; |
- DCHECK_GE(MainThreadOnly().timer_queue_suspend_count_, 0); |
+ MainThreadOnly().timer_queue_suspend_count--; |
+ DCHECK_GE(MainThreadOnly().timer_queue_suspend_count, 0); |
ForceUpdatePolicy(); |
} |
void RendererSchedulerImpl::SetTimerQueueSuspensionWhenBackgroundedEnabled( |
bool enabled) { |
// Note that this will only take effect for the next backgrounded signal. |
- MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled_ = enabled; |
-} |
- |
-// static |
-const char* RendererSchedulerImpl::PolicyToString(Policy policy) { |
- switch (policy) { |
- case Policy::NORMAL: |
- return "normal"; |
- case Policy::COMPOSITOR_PRIORITY: |
- return "compositor"; |
- case Policy::COMPOSITOR_CRITICAL_PATH_PRIORITY: |
- return "compositor_critical_path"; |
- case Policy::TOUCHSTART_PRIORITY: |
- return "touchstart"; |
- case Policy::LOADING_PRIORITY: |
- return "loading"; |
- default: |
- NOTREACHED(); |
- return nullptr; |
- } |
+ MainThreadOnly().timer_queue_suspension_when_backgrounded_enabled = enabled; |
} |
scoped_refptr<base::trace_event::ConvertableToTraceFormat> |
@@ -690,95 +736,117 @@ RendererSchedulerImpl::AsValueLocked(base::TimeTicks optional_now) const { |
scoped_refptr<base::trace_event::TracedValue> state = |
new base::trace_event::TracedValue(); |
- state->SetString("current_policy", |
- PolicyToString(MainThreadOnly().current_policy_)); |
+ state->SetString("current_use_case", |
+ UseCaseToString(MainThreadOnly().current_use_case)); |
+ state->SetBoolean("loading_tasks_seem_expensive", |
+ MainThreadOnly().loading_tasks_seem_expensive); |
+ state->SetBoolean("timer_tasks_seem_expensive", |
+ MainThreadOnly().timer_tasks_seem_expensive); |
+ state->SetBoolean("touchstart_expected_soon", |
+ MainThreadOnly().touchstart_expected_soon); |
state->SetString("idle_period_state", |
IdleHelper::IdlePeriodStateToString( |
idle_helper_.SchedulerIdlePeriodState())); |
- state->SetBoolean("renderer_hidden", MainThreadOnly().renderer_hidden_); |
+ state->SetBoolean("renderer_hidden", MainThreadOnly().renderer_hidden); |
state->SetBoolean("renderer_backgrounded", |
- MainThreadOnly().renderer_backgrounded_); |
+ MainThreadOnly().renderer_backgrounded); |
state->SetBoolean("timer_queue_suspended_when_backgrounded", |
- MainThreadOnly().timer_queue_suspended_when_backgrounded_); |
+ MainThreadOnly().timer_queue_suspended_when_backgrounded); |
state->SetInteger("timer_queue_suspend_count", |
- MainThreadOnly().timer_queue_suspend_count_); |
+ MainThreadOnly().timer_queue_suspend_count); |
state->SetDouble("now", (optional_now - base::TimeTicks()).InMillisecondsF()); |
- state->SetDouble("last_input_signal_time", |
- (AnyThread().last_input_signal_time_ - base::TimeTicks()) |
- .InMillisecondsF()); |
- state->SetDouble("rails_loading_priority_deadline", |
- (AnyThread().rails_loading_priority_deadline_ - |
- base::TimeTicks()).InMillisecondsF()); |
+ state->SetDouble( |
+ "rails_loading_priority_deadline", |
+ (AnyThread().rails_loading_priority_deadline - base::TimeTicks()) |
+ .InMillisecondsF()); |
state->SetDouble("last_idle_period_end_time", |
- (AnyThread().last_idle_period_end_time_ - base::TimeTicks()) |
+ (AnyThread().last_idle_period_end_time - base::TimeTicks()) |
.InMillisecondsF()); |
- state->SetInteger("pending_main_thread_input_event_count", |
- AnyThread().pending_main_thread_input_event_count_); |
state->SetBoolean("awaiting_touch_start_response", |
- AnyThread().awaiting_touch_start_response_); |
+ AnyThread().awaiting_touch_start_response); |
state->SetBoolean("begin_main_frame_on_critical_path", |
- AnyThread().begin_main_frame_on_critical_path_); |
+ AnyThread().begin_main_frame_on_critical_path); |
+ state->SetDouble("expected_loading_task_duration", |
+ MainThreadOnly() |
+ .loading_task_cost_estimator.expected_task_duration() |
+ .InMillisecondsF()); |
state->SetDouble("expected_timer_task_duration", |
MainThreadOnly() |
- .timer_task_cost_estimator_.expected_task_duration() |
+ .timer_task_cost_estimator.expected_task_duration() |
.InMillisecondsF()); |
// TODO(skyostil): Can we somehow trace how accurate these estimates were? |
state->SetDouble( |
"expected_short_idle_period_duration", |
- MainThreadOnly().expected_short_idle_period_duration_.InMillisecondsF()); |
- state->SetBoolean("timer_tasks_seem_expensive", |
- AnyThread().timer_tasks_seem_expensive_); |
- state->SetDouble("estimated_next_frame_begin", |
- (MainThreadOnly().estimated_next_frame_begin_ - |
- base::TimeTicks()).InMillisecondsF()); |
- state->SetBoolean("in_idle_period", AnyThread().in_idle_period_); |
+ MainThreadOnly().expected_short_idle_period_duration.InMillisecondsF()); |
+ state->SetDouble( |
+ "estimated_next_frame_begin", |
+ (MainThreadOnly().estimated_next_frame_begin - base::TimeTicks()) |
+ .InMillisecondsF()); |
+ state->SetBoolean("in_idle_period", AnyThread().in_idle_period); |
+ AnyThread().user_model.AsValueInto(state.get()); |
return state; |
} |
void RendererSchedulerImpl::OnIdlePeriodStarted() { |
base::AutoLock lock(any_thread_lock_); |
- AnyThread().in_idle_period_ = true; |
+ AnyThread().in_idle_period = true; |
UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); |
} |
void RendererSchedulerImpl::OnIdlePeriodEnded() { |
base::AutoLock lock(any_thread_lock_); |
- AnyThread().last_idle_period_end_time_ = helper_.Now(); |
- AnyThread().in_idle_period_ = false; |
+ AnyThread().last_idle_period_end_time = helper_.Now(); |
+ AnyThread().in_idle_period = false; |
UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); |
} |
void RendererSchedulerImpl::OnPageLoadStarted() { |
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), |
+ "RendererSchedulerImpl::OnPageLoadStarted"); |
base::AutoLock lock(any_thread_lock_); |
- AnyThread().rails_loading_priority_deadline_ = |
+ AnyThread().rails_loading_priority_deadline = |
helper_.Now() + base::TimeDelta::FromMilliseconds( |
kRailsInitialLoadingPrioritizationMillis); |
- UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); |
+ ResetForNavigationLocked(); |
} |
bool RendererSchedulerImpl::HadAnIdlePeriodRecently(base::TimeTicks now) const { |
- return (now - AnyThread().last_idle_period_end_time_) <= |
+ return (now - AnyThread().last_idle_period_end_time) <= |
base::TimeDelta::FromMilliseconds( |
kIdlePeriodStarvationThresholdMillis); |
} |
void RendererSchedulerImpl::SuspendTimerQueueWhenBackgrounded() { |
- DCHECK(MainThreadOnly().renderer_backgrounded_); |
- if (MainThreadOnly().timer_queue_suspended_when_backgrounded_) |
+ DCHECK(MainThreadOnly().renderer_backgrounded); |
+ if (MainThreadOnly().timer_queue_suspended_when_backgrounded) |
return; |
- MainThreadOnly().timer_queue_suspended_when_backgrounded_ = true; |
+ MainThreadOnly().timer_queue_suspended_when_backgrounded = true; |
ForceUpdatePolicy(); |
} |
void RendererSchedulerImpl::ResumeTimerQueueWhenForegrounded() { |
- DCHECK(!MainThreadOnly().renderer_backgrounded_); |
- if (!MainThreadOnly().timer_queue_suspended_when_backgrounded_) |
+ DCHECK(!MainThreadOnly().renderer_backgrounded); |
+ if (!MainThreadOnly().timer_queue_suspended_when_backgrounded) |
return; |
- MainThreadOnly().timer_queue_suspended_when_backgrounded_ = false; |
+ MainThreadOnly().timer_queue_suspended_when_backgrounded = false; |
ForceUpdatePolicy(); |
} |
+void RendererSchedulerImpl::ResetForNavigationLocked() { |
+ helper_.CheckOnValidThread(); |
+ any_thread_lock_.AssertAcquired(); |
+ MainThreadOnly().loading_task_cost_estimator.Clear(); |
+ MainThreadOnly().timer_task_cost_estimator.Clear(); |
+ MainThreadOnly().short_idle_period_duration.Clear(); |
+ // Make sure that we don't initially assume there is no idle time. |
+ MainThreadOnly().short_idle_period_duration.InsertSample( |
+ cc::BeginFrameArgs::DefaultInterval()); |
+ AnyThread().user_model.Reset(); |
+ MainThreadOnly().have_seen_a_begin_main_frame = false; |
+ UpdatePolicyLocked(UpdateType::MAY_EARLY_OUT_IF_POLICY_UNCHANGED); |
+} |
+ |
} // namespace scheduler |