// 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 "components/scheduler/renderer/throttling_helper.h" #include "base/logging.h" #include "components/scheduler/base/real_time_domain.h" #include "components/scheduler/child/scheduler_tqm_delegate.h" #include "components/scheduler/renderer/renderer_scheduler_impl.h" #include "components/scheduler/renderer/throttled_time_domain.h" #include "components/scheduler/renderer/web_frame_scheduler_impl.h" #include "third_party/WebKit/public/platform/WebFrameScheduler.h" namespace scheduler { ThrottlingHelper::ThrottlingHelper(RendererSchedulerImpl* renderer_scheduler, const char* tracing_category) : task_runner_(renderer_scheduler->ControlTaskRunner()), renderer_scheduler_(renderer_scheduler), tick_clock_(renderer_scheduler->tick_clock()), tracing_category_(tracing_category), time_domain_(new ThrottledTimeDomain(this, tick_clock_)), weak_factory_(this) { suspend_timers_when_backgrounded_closure_.Reset(base::Bind( &ThrottlingHelper::PumpThrottledTasks, weak_factory_.GetWeakPtr())); forward_immediate_work_closure_ = base::Bind(&ThrottlingHelper::OnTimeDomainHasImmediateWork, weak_factory_.GetWeakPtr()); renderer_scheduler_->RegisterTimeDomain(time_domain_.get()); } ThrottlingHelper::~ThrottlingHelper() { // It's possible for queues to be still throttled, so we need to tidy up // before unregistering the time domain. for (const TaskQueueMap::value_type& map_entry : throttled_queues_) { TaskQueue* task_queue = map_entry.first; task_queue->SetTimeDomain(renderer_scheduler_->real_time_domain()); task_queue->SetPumpPolicy(TaskQueue::PumpPolicy::AUTO); } renderer_scheduler_->UnregisterTimeDomain(time_domain_.get()); } void ThrottlingHelper::IncreaseThrottleRefCount(TaskQueue* task_queue) { DCHECK_NE(task_queue, task_runner_.get()); std::pair insert_result = throttled_queues_.insert(std::make_pair(task_queue, 1)); if (insert_result.second) { // The insert was succesful so we need to throttle the queue. task_queue->SetTimeDomain(time_domain_.get()); task_queue->SetPumpPolicy(TaskQueue::PumpPolicy::MANUAL); if (!task_queue->IsEmpty()) { if (task_queue->HasPendingImmediateWork()) { OnTimeDomainHasImmediateWork(); } else { OnTimeDomainHasDelayedWork(); } } } else { // An entry already existed in the map so we need to increment the refcount. insert_result.first->second++; } } void ThrottlingHelper::DecreaseThrottleRefCount(TaskQueue* task_queue) { TaskQueueMap::iterator iter = throttled_queues_.find(task_queue); if (iter != throttled_queues_.end() && --iter->second == 0) { // The refcount has become zero, we need to unthrottle the queue. throttled_queues_.erase(iter); task_queue->SetTimeDomain(renderer_scheduler_->real_time_domain()); task_queue->SetPumpPolicy(TaskQueue::PumpPolicy::AUTO); } } void ThrottlingHelper::UnregisterTaskQueue(TaskQueue* task_queue) { throttled_queues_.erase(task_queue); } void ThrottlingHelper::OnTimeDomainHasImmediateWork() { // Forward to the main thread if called from another thread. if (!task_runner_->RunsTasksOnCurrentThread()) { task_runner_->PostTask(FROM_HERE, forward_immediate_work_closure_); return; } TRACE_EVENT0(tracing_category_, "ThrottlingHelper::OnTimeDomainHasImmediateWork"); base::TimeTicks now = tick_clock_->NowTicks(); MaybeSchedulePumpThrottledTasksLocked(FROM_HERE, now, now); } void ThrottlingHelper::OnTimeDomainHasDelayedWork() { TRACE_EVENT0(tracing_category_, "ThrottlingHelper::OnTimeDomainHasDelayedWork"); base::TimeTicks next_scheduled_delayed_task; bool has_delayed_task = time_domain_->NextScheduledRunTime(&next_scheduled_delayed_task); DCHECK(has_delayed_task); base::TimeTicks now = tick_clock_->NowTicks(); MaybeSchedulePumpThrottledTasksLocked(FROM_HERE, now, next_scheduled_delayed_task); } void ThrottlingHelper::PumpThrottledTasks() { TRACE_EVENT0(tracing_category_, "ThrottlingHelper::PumpThrottledTasks"); pending_pump_throttled_tasks_runtime_ = base::TimeTicks(); base::TimeTicks now = tick_clock_->NowTicks(); time_domain_->AdvanceTo(now); for (const TaskQueueMap::value_type& map_entry : throttled_queues_) { TaskQueue* task_queue = map_entry.first; if (task_queue->IsEmpty()) continue; task_queue->PumpQueue(false); } // Make sure NextScheduledRunTime gives us an up-to date result. time_domain_->ClearExpiredWakeups(); base::TimeTicks next_scheduled_delayed_task; // Maybe schedule a call to ThrottlingHelper::PumpThrottledTasks if there is // a pending delayed task. NOTE posting a non-delayed task in the future will // result in ThrottlingHelper::OnTimeDomainHasImmediateWork being called. if (time_domain_->NextScheduledRunTime(&next_scheduled_delayed_task)) { MaybeSchedulePumpThrottledTasksLocked(FROM_HERE, now, next_scheduled_delayed_task); } } /* static */ base::TimeTicks ThrottlingHelper::ThrottledRunTime( base::TimeTicks unthrottled_runtime) { const base::TimeDelta one_second = base::TimeDelta::FromSeconds(1); return unthrottled_runtime + one_second - ((unthrottled_runtime - base::TimeTicks()) % one_second); } void ThrottlingHelper::MaybeSchedulePumpThrottledTasksLocked( const tracked_objects::Location& from_here, base::TimeTicks now, base::TimeTicks unthrottled_runtime) { base::TimeTicks throttled_runtime = ThrottledRunTime(std::max(now, unthrottled_runtime)); // If there is a pending call to PumpThrottledTasks and it's sooner than // |unthrottled_runtime| then return. if (!pending_pump_throttled_tasks_runtime_.is_null() && throttled_runtime >= pending_pump_throttled_tasks_runtime_) { return; } pending_pump_throttled_tasks_runtime_ = throttled_runtime; suspend_timers_when_backgrounded_closure_.Cancel(); base::TimeDelta delay = pending_pump_throttled_tasks_runtime_ - now; TRACE_EVENT1(tracing_category_, "ThrottlingHelper::MaybeSchedulePumpThrottledTasksLocked", "delay_till_next_pump_ms", delay.InMilliseconds()); task_runner_->PostDelayedTask( from_here, suspend_timers_when_backgrounded_closure_.callback(), delay); } } // namespace scheduler