blob: 4b8998206ec8fc76d92020e4174d38100914f11b [file] [log] [blame]
// 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/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/traced_value.h"
namespace base {
namespace sequence_manager {
namespace internal {
TaskQueueSelector::TaskQueueSelector(
scoped_refptr<AssociatedThreadId> associated_thread)
: associated_thread_(std::move(associated_thread)),
delayed_work_queue_sets_(TaskQueue::kQueuePriorityCount, "delayed"),
immediate_work_queue_sets_(TaskQueue::kQueuePriorityCount, "immediate") {}
TaskQueueSelector::~TaskQueueSelector() = default;
void TaskQueueSelector::AddQueue(internal::TaskQueueImpl* queue) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK(queue->IsQueueEnabled());
AddQueueImpl(queue, TaskQueue::kNormalPriority);
}
void TaskQueueSelector::RemoveQueue(internal::TaskQueueImpl* queue) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
if (queue->IsQueueEnabled()) {
RemoveQueueImpl(queue);
}
}
void TaskQueueSelector::EnableQueue(internal::TaskQueueImpl* queue) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK(queue->IsQueueEnabled());
AddQueueImpl(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());
RemoveQueueImpl(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()) {
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);
}
void TaskQueueSelector::AddQueueImpl(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::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::RemoveQueueImpl(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
}
WorkQueue* TaskQueueSelector::ChooseOldestImmediateTaskWithPriority(
TaskQueue::QueuePriority priority) const {
return immediate_work_queue_sets_.GetOldestQueueInSet(priority);
}
WorkQueue* TaskQueueSelector::ChooseOldestDelayedTaskWithPriority(
TaskQueue::QueuePriority priority) const {
return delayed_work_queue_sets_.GetOldestQueueInSet(priority);
}
WorkQueue* TaskQueueSelector::ChooseOldestImmediateOrDelayedTaskWithPriority(
TaskQueue::QueuePriority priority) const {
EnqueueOrder immediate_enqueue_order;
WorkQueue* immediate_queue =
immediate_work_queue_sets_.GetOldestQueueAndEnqueueOrderInSet(
priority, &immediate_enqueue_order);
EnqueueOrder delayed_enqueue_order;
WorkQueue* delayed_queue =
delayed_work_queue_sets_.GetOldestQueueAndEnqueueOrderInSet(
priority, &delayed_enqueue_order);
if (!delayed_queue)
return immediate_queue;
if (immediate_queue && immediate_enqueue_order < delayed_enqueue_order)
return immediate_queue;
return delayed_queue;
}
WorkQueue* TaskQueueSelector::ChooseOldestWithPriority(
TaskQueue::QueuePriority priority) const {
if (choose_immediate_over_delayed_) {
WorkQueue* queue = ChooseOldestImmediateTaskWithPriority(priority);
if (queue)
return queue;
return ChooseOldestDelayedTaskWithPriority(priority);
} else {
return ChooseOldestImmediateOrDelayedTaskWithPriority(priority);
}
}
WorkQueue* TaskQueueSelector::SelectWorkQueueToServiceImpl(
TaskQueue::QueuePriority max_priority) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
// Always service the control queue if it has any work.
if (max_priority > TaskQueue::kControlPriority) {
WorkQueue* queue = ChooseOldestWithPriority(TaskQueue::kControlPriority);
if (queue)
return queue;
}
// Select from the low priority queue if we are starving it.
if (max_priority > TaskQueue::kLowPriority &&
low_priority_starvation_score_ >= kMaxLowPriorityStarvationScore) {
WorkQueue* queue = ChooseOldestWithPriority(TaskQueue::kLowPriority);
if (queue)
return queue;
}
// Select from the normal priority queue if we are starving it.
if (max_priority > TaskQueue::kNormalPriority &&
normal_priority_starvation_score_ >= kMaxNormalPriorityStarvationScore) {
WorkQueue* queue = ChooseOldestWithPriority(TaskQueue::kNormalPriority);
if (queue)
return queue;
}
// Select from the high priority queue if we are starving it.
if (max_priority > TaskQueue::kHighPriority &&
high_priority_starvation_score_ >= kMaxHighPriorityStarvationScore) {
WorkQueue* queue = ChooseOldestWithPriority(TaskQueue::kHighPriority);
if (queue)
return queue;
}
// Otherwise choose in priority order.
for (TaskQueue::QueuePriority priority = TaskQueue::kHighestPriority;
priority < max_priority; priority = NextPriority(priority)) {
WorkQueue* queue = ChooseOldestWithPriority(priority);
if (queue)
return queue;
}
return nullptr;
}
#if DCHECK_IS_ON() || !defined(NDEBUG)
bool TaskQueueSelector::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
WorkQueue* TaskQueueSelector::SelectWorkQueueToService() {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
WorkQueue* queue =
SelectWorkQueueToServiceImpl(TaskQueue::kQueuePriorityCount);
if (queue) {
// 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>(queue->work_queue_set_index()),
queue->work_queue_sets() == &delayed_work_queue_sets_);
}
return queue;
}
void TaskQueueSelector::DidSelectQueueWithPriority(
TaskQueue::QueuePriority priority,
bool chose_delayed_queue) {
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();
}
// Achieve delayed and immediate task round robining by favoring immediate
// tasks if we just selected a delayed task.
choose_immediate_over_delayed_ = chose_delayed_queue;
}
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->SetBoolean("choose_immediate_over_delayed",
choose_immediate_over_delayed_);
}
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 (!delayed_work_queue_sets_.IsSetEmpty(priority) ||
!immediate_work_queue_sets_.IsSetEmpty(priority)) {
return false;
}
}
return true;
}
bool TaskQueueSelector::HasTasksWithPriority(
TaskQueue::QueuePriority priority) {
return !delayed_work_queue_sets_.IsSetEmpty(priority) ||
!immediate_work_queue_sets_.IsSetEmpty(priority);
}
} // namespace internal
} // namespace sequence_manager
} // namespace base