|  | // 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 <utility> | 
|  |  | 
|  | #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<AssociatedThreadId> 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<TaskQueue::QueuePriority>(static_cast<int>(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<TaskQueue::QueuePriority>( | 
|  | (*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 |