// Copyright 2014 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 "base/task/sequence_manager/task_queue_selector.h" #include #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/task/sequence_manager/associated_thread_id.h" #include "base/task/sequence_manager/task_queue_impl.h" #include "base/task/sequence_manager/work_queue.h" #include "base/threading/thread_checker.h" #include "base/trace_event/trace_event_argument.h" namespace base { namespace sequence_manager { namespace internal { namespace { TaskQueueSelectorLogic QueuePriorityToSelectorLogic( TaskQueue::QueuePriority priority) { switch (priority) { case TaskQueue::kControlPriority: return TaskQueueSelectorLogic::kControlPriorityLogic; case TaskQueue::kHighestPriority: return TaskQueueSelectorLogic::kHighestPriorityLogic; case TaskQueue::kHighPriority: return TaskQueueSelectorLogic::kHighPriorityLogic; case TaskQueue::kNormalPriority: return TaskQueueSelectorLogic::kNormalPriorityLogic; case TaskQueue::kLowPriority: return TaskQueueSelectorLogic::kLowPriorityLogic; case TaskQueue::kBestEffortPriority: return TaskQueueSelectorLogic::kBestEffortPriorityLogic; default: NOTREACHED(); return TaskQueueSelectorLogic::kCount; } } // Helper function used to report the number of times a selector logic is // trigerred. This will create a histogram for the enumerated data. void ReportTaskSelectionLogic(TaskQueueSelectorLogic selector_logic) { UMA_HISTOGRAM_ENUMERATION("TaskQueueSelector.TaskServicedPerSelectorLogic", selector_logic, TaskQueueSelectorLogic::kCount); } } // namespace TaskQueueSelector::TaskQueueSelector( scoped_refptr associated_thread) : associated_thread_(std::move(associated_thread)), prioritizing_selector_(this, "enabled"), immediate_starvation_count_(0), high_priority_starvation_score_(0), normal_priority_starvation_score_(0), low_priority_starvation_score_(0), task_queue_selector_observer_(nullptr) {} TaskQueueSelector::~TaskQueueSelector() = default; void TaskQueueSelector::AddQueue(internal::TaskQueueImpl* queue) { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); DCHECK(queue->IsQueueEnabled()); prioritizing_selector_.AddQueue(queue, TaskQueue::kNormalPriority); } void TaskQueueSelector::RemoveQueue(internal::TaskQueueImpl* queue) { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); if (queue->IsQueueEnabled()) { prioritizing_selector_.RemoveQueue(queue); } } void TaskQueueSelector::EnableQueue(internal::TaskQueueImpl* queue) { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); DCHECK(queue->IsQueueEnabled()); prioritizing_selector_.AddQueue(queue, queue->GetQueuePriority()); if (task_queue_selector_observer_) task_queue_selector_observer_->OnTaskQueueEnabled(queue); } void TaskQueueSelector::DisableQueue(internal::TaskQueueImpl* queue) { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); DCHECK(!queue->IsQueueEnabled()); prioritizing_selector_.RemoveQueue(queue); } void TaskQueueSelector::SetQueuePriority(internal::TaskQueueImpl* queue, TaskQueue::QueuePriority priority) { DCHECK_LT(priority, TaskQueue::kQueuePriorityCount); DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); if (queue->IsQueueEnabled()) { prioritizing_selector_.ChangeSetIndex(queue, priority); } else { // Disabled queue is not in any set so we can't use ChangeSetIndex here // and have to assign priority for the queue itself. queue->delayed_work_queue()->AssignSetIndex(priority); queue->immediate_work_queue()->AssignSetIndex(priority); } DCHECK_EQ(priority, queue->GetQueuePriority()); } TaskQueue::QueuePriority TaskQueueSelector::NextPriority( TaskQueue::QueuePriority priority) { DCHECK(priority < TaskQueue::kQueuePriorityCount); return static_cast(static_cast(priority) + 1); } TaskQueueSelector::PrioritizingSelector::PrioritizingSelector( TaskQueueSelector* task_queue_selector, const char* name) : task_queue_selector_(task_queue_selector), delayed_work_queue_sets_(TaskQueue::kQueuePriorityCount, name), immediate_work_queue_sets_(TaskQueue::kQueuePriorityCount, name) {} void TaskQueueSelector::PrioritizingSelector::AddQueue( internal::TaskQueueImpl* queue, TaskQueue::QueuePriority priority) { #if DCHECK_IS_ON() DCHECK(!CheckContainsQueueForTest(queue)); #endif delayed_work_queue_sets_.AddQueue(queue->delayed_work_queue(), priority); immediate_work_queue_sets_.AddQueue(queue->immediate_work_queue(), priority); #if DCHECK_IS_ON() DCHECK(CheckContainsQueueForTest(queue)); #endif } void TaskQueueSelector::PrioritizingSelector::ChangeSetIndex( internal::TaskQueueImpl* queue, TaskQueue::QueuePriority priority) { #if DCHECK_IS_ON() DCHECK(CheckContainsQueueForTest(queue)); #endif delayed_work_queue_sets_.ChangeSetIndex(queue->delayed_work_queue(), priority); immediate_work_queue_sets_.ChangeSetIndex(queue->immediate_work_queue(), priority); #if DCHECK_IS_ON() DCHECK(CheckContainsQueueForTest(queue)); #endif } void TaskQueueSelector::PrioritizingSelector::RemoveQueue( internal::TaskQueueImpl* queue) { #if DCHECK_IS_ON() DCHECK(CheckContainsQueueForTest(queue)); #endif delayed_work_queue_sets_.RemoveQueue(queue->delayed_work_queue()); immediate_work_queue_sets_.RemoveQueue(queue->immediate_work_queue()); #if DCHECK_IS_ON() DCHECK(!CheckContainsQueueForTest(queue)); #endif } bool TaskQueueSelector::PrioritizingSelector:: ChooseOldestImmediateTaskWithPriority(TaskQueue::QueuePriority priority, WorkQueue** out_work_queue) const { return immediate_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue); } bool TaskQueueSelector::PrioritizingSelector:: ChooseOldestDelayedTaskWithPriority(TaskQueue::QueuePriority priority, WorkQueue** out_work_queue) const { return delayed_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue); } bool TaskQueueSelector::PrioritizingSelector:: ChooseOldestImmediateOrDelayedTaskWithPriority( TaskQueue::QueuePriority priority, bool* out_chose_delayed_over_immediate, WorkQueue** out_work_queue) const { WorkQueue* immediate_queue; DCHECK_EQ(*out_chose_delayed_over_immediate, false); EnqueueOrder immediate_enqueue_order; if (immediate_work_queue_sets_.GetOldestQueueAndEnqueueOrderInSet( priority, &immediate_queue, &immediate_enqueue_order)) { WorkQueue* delayed_queue; EnqueueOrder delayed_enqueue_order; if (delayed_work_queue_sets_.GetOldestQueueAndEnqueueOrderInSet( priority, &delayed_queue, &delayed_enqueue_order)) { if (immediate_enqueue_order < delayed_enqueue_order) { *out_work_queue = immediate_queue; } else { *out_chose_delayed_over_immediate = true; *out_work_queue = delayed_queue; } } else { *out_work_queue = immediate_queue; } return true; } return delayed_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue); } bool TaskQueueSelector::PrioritizingSelector::ChooseOldestWithPriority( TaskQueue::QueuePriority priority, bool* out_chose_delayed_over_immediate, WorkQueue** out_work_queue) const { // Select an immediate work queue if we are starving immediate tasks. if (task_queue_selector_->immediate_starvation_count_ >= kMaxDelayedStarvationTasks) { if (ChooseOldestImmediateTaskWithPriority(priority, out_work_queue)) return true; return ChooseOldestDelayedTaskWithPriority(priority, out_work_queue); } return ChooseOldestImmediateOrDelayedTaskWithPriority( priority, out_chose_delayed_over_immediate, out_work_queue); } bool TaskQueueSelector::PrioritizingSelector::SelectWorkQueueToService( TaskQueue::QueuePriority max_priority, WorkQueue** out_work_queue, bool* out_chose_delayed_over_immediate) { DCHECK_CALLED_ON_VALID_THREAD( task_queue_selector_->associated_thread_->thread_checker); DCHECK_EQ(*out_chose_delayed_over_immediate, false); // Always service the control queue if it has any work. if (max_priority > TaskQueue::kControlPriority && ChooseOldestWithPriority(TaskQueue::kControlPriority, out_chose_delayed_over_immediate, out_work_queue)) { ReportTaskSelectionLogic(TaskQueueSelectorLogic::kControlPriorityLogic); return true; } // Select from the low priority queue if we are starving it. if (max_priority > TaskQueue::kLowPriority && task_queue_selector_->low_priority_starvation_score_ >= kMaxLowPriorityStarvationScore && ChooseOldestWithPriority(TaskQueue::kLowPriority, out_chose_delayed_over_immediate, out_work_queue)) { ReportTaskSelectionLogic( TaskQueueSelectorLogic::kLowPriorityStarvationLogic); return true; } // Select from the normal priority queue if we are starving it. if (max_priority > TaskQueue::kNormalPriority && task_queue_selector_->normal_priority_starvation_score_ >= kMaxNormalPriorityStarvationScore && ChooseOldestWithPriority(TaskQueue::kNormalPriority, out_chose_delayed_over_immediate, out_work_queue)) { ReportTaskSelectionLogic( TaskQueueSelectorLogic::kNormalPriorityStarvationLogic); return true; } // Select from the high priority queue if we are starving it. if (max_priority > TaskQueue::kHighPriority && task_queue_selector_->high_priority_starvation_score_ >= kMaxHighPriorityStarvationScore && ChooseOldestWithPriority(TaskQueue::kHighPriority, out_chose_delayed_over_immediate, out_work_queue)) { ReportTaskSelectionLogic( TaskQueueSelectorLogic::kHighPriorityStarvationLogic); return true; } // Otherwise choose in priority order. for (TaskQueue::QueuePriority priority = TaskQueue::kHighestPriority; priority < max_priority; priority = NextPriority(priority)) { if (ChooseOldestWithPriority(priority, out_chose_delayed_over_immediate, out_work_queue)) { ReportTaskSelectionLogic(QueuePriorityToSelectorLogic(priority)); return true; } } return false; } #if DCHECK_IS_ON() || !defined(NDEBUG) bool TaskQueueSelector::PrioritizingSelector::CheckContainsQueueForTest( const internal::TaskQueueImpl* queue) const { bool contains_delayed_work_queue = delayed_work_queue_sets_.ContainsWorkQueueForTest( queue->delayed_work_queue()); bool contains_immediate_work_queue = immediate_work_queue_sets_.ContainsWorkQueueForTest( queue->immediate_work_queue()); DCHECK_EQ(contains_delayed_work_queue, contains_immediate_work_queue); return contains_delayed_work_queue; } #endif bool TaskQueueSelector::SelectWorkQueueToService(WorkQueue** out_work_queue) { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); bool chose_delayed_over_immediate = false; bool found_queue = prioritizing_selector_.SelectWorkQueueToService( TaskQueue::kQueuePriorityCount, out_work_queue, &chose_delayed_over_immediate); if (!found_queue) return false; // We could use |(*out_work_queue)->task_queue()->GetQueuePriority()| here but // for re-queued non-nestable tasks |task_queue()| returns null. DidSelectQueueWithPriority(static_cast( (*out_work_queue)->work_queue_set_index()), chose_delayed_over_immediate); return true; } void TaskQueueSelector::DidSelectQueueWithPriority( TaskQueue::QueuePriority priority, bool chose_delayed_over_immediate) { switch (priority) { case TaskQueue::kControlPriority: break; case TaskQueue::kHighestPriority: low_priority_starvation_score_ += HasTasksWithPriority(TaskQueue::kLowPriority) ? kSmallScoreIncrementForLowPriorityStarvation : 0; normal_priority_starvation_score_ += HasTasksWithPriority(TaskQueue::kNormalPriority) ? kSmallScoreIncrementForNormalPriorityStarvation : 0; high_priority_starvation_score_ += HasTasksWithPriority(TaskQueue::kHighPriority) ? kSmallScoreIncrementForHighPriorityStarvation : 0; break; case TaskQueue::kHighPriority: low_priority_starvation_score_ += HasTasksWithPriority(TaskQueue::kLowPriority) ? kLargeScoreIncrementForLowPriorityStarvation : 0; normal_priority_starvation_score_ += HasTasksWithPriority(TaskQueue::kNormalPriority) ? kLargeScoreIncrementForNormalPriorityStarvation : 0; high_priority_starvation_score_ = 0; break; case TaskQueue::kNormalPriority: low_priority_starvation_score_ += HasTasksWithPriority(TaskQueue::kLowPriority) ? kLargeScoreIncrementForLowPriorityStarvation : 0; normal_priority_starvation_score_ = 0; break; case TaskQueue::kLowPriority: case TaskQueue::kBestEffortPriority: low_priority_starvation_score_ = 0; high_priority_starvation_score_ = 0; normal_priority_starvation_score_ = 0; break; default: NOTREACHED(); } if (chose_delayed_over_immediate) { immediate_starvation_count_++; } else { immediate_starvation_count_ = 0; } } void TaskQueueSelector::AsValueInto(trace_event::TracedValue* state) const { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); state->SetInteger("high_priority_starvation_score", high_priority_starvation_score_); state->SetInteger("normal_priority_starvation_score", normal_priority_starvation_score_); state->SetInteger("low_priority_starvation_score", low_priority_starvation_score_); state->SetInteger("immediate_starvation_count", immediate_starvation_count_); } void TaskQueueSelector::SetTaskQueueSelectorObserver(Observer* observer) { task_queue_selector_observer_ = observer; } bool TaskQueueSelector::AllEnabledWorkQueuesAreEmpty() const { DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker); for (TaskQueue::QueuePriority priority = TaskQueue::kControlPriority; priority < TaskQueue::kQueuePriorityCount; priority = NextPriority(priority)) { if (!prioritizing_selector_.delayed_work_queue_sets()->IsSetEmpty( priority) || !prioritizing_selector_.immediate_work_queue_sets()->IsSetEmpty( priority)) { return false; } } return true; } void TaskQueueSelector::SetImmediateStarvationCountForTest( size_t immediate_starvation_count) { immediate_starvation_count_ = immediate_starvation_count; } bool TaskQueueSelector::HasTasksWithPriority( TaskQueue::QueuePriority priority) { return !prioritizing_selector_.delayed_work_queue_sets()->IsSetEmpty( priority) || !prioritizing_selector_.immediate_work_queue_sets()->IsSetEmpty( priority); } } // namespace internal } // namespace sequence_manager } // namespace base