331 lines
10 KiB
C++
331 lines
10 KiB
C++
|
|
// Copyright 2015 The Chromium Authors
|
||
|
|
// 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/work_queue.h"
|
||
|
|
|
||
|
|
#include "base/containers/stack_container.h"
|
||
|
|
#include "base/debug/alias.h"
|
||
|
|
#include "base/task/sequence_manager/fence.h"
|
||
|
|
#include "base/task/sequence_manager/sequence_manager_impl.h"
|
||
|
|
#include "base/task/sequence_manager/task_order.h"
|
||
|
|
#include "base/task/sequence_manager/work_queue_sets.h"
|
||
|
|
#include "build/build_config.h"
|
||
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
namespace sequence_manager {
|
||
|
|
namespace internal {
|
||
|
|
|
||
|
|
WorkQueue::WorkQueue(TaskQueueImpl* task_queue,
|
||
|
|
const char* name,
|
||
|
|
QueueType queue_type)
|
||
|
|
: task_queue_(task_queue), name_(name), queue_type_(queue_type) {}
|
||
|
|
|
||
|
|
Value::List WorkQueue::AsValue(TimeTicks now) const {
|
||
|
|
Value::List state;
|
||
|
|
for (const Task& task : tasks_)
|
||
|
|
state.Append(TaskQueueImpl::TaskAsValue(task, now));
|
||
|
|
return state;
|
||
|
|
}
|
||
|
|
|
||
|
|
WorkQueue::~WorkQueue() {
|
||
|
|
DCHECK(!work_queue_sets_) << task_queue_->GetName() << " : "
|
||
|
|
<< work_queue_sets_->GetName() << " : " << name_;
|
||
|
|
}
|
||
|
|
|
||
|
|
const Task* WorkQueue::GetFrontTask() const {
|
||
|
|
if (tasks_.empty())
|
||
|
|
return nullptr;
|
||
|
|
return &tasks_.front();
|
||
|
|
}
|
||
|
|
|
||
|
|
const Task* WorkQueue::GetBackTask() const {
|
||
|
|
if (tasks_.empty())
|
||
|
|
return nullptr;
|
||
|
|
return &tasks_.back();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool WorkQueue::BlockedByFence() const {
|
||
|
|
if (!fence_)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// If the queue is empty then any future tasks will have a higher enqueue
|
||
|
|
// order and will be blocked. The queue is also blocked if the head is past
|
||
|
|
// the fence.
|
||
|
|
return tasks_.empty() || tasks_.front().task_order() >= fence_->task_order();
|
||
|
|
}
|
||
|
|
|
||
|
|
absl::optional<TaskOrder> WorkQueue::GetFrontTaskOrder() const {
|
||
|
|
if (tasks_.empty() || BlockedByFence())
|
||
|
|
return absl::nullopt;
|
||
|
|
// Quick sanity check.
|
||
|
|
DCHECK(tasks_.front().task_order() <= tasks_.back().task_order())
|
||
|
|
<< task_queue_->GetName() << " : " << work_queue_sets_->GetName() << " : "
|
||
|
|
<< name_;
|
||
|
|
return tasks_.front().task_order();
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::Push(Task task) {
|
||
|
|
bool was_empty = tasks_.empty();
|
||
|
|
#ifndef NDEBUG
|
||
|
|
DCHECK(task.enqueue_order_set());
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Make sure the task order is strictly increasing.
|
||
|
|
DCHECK(was_empty || tasks_.back().task_order() < task.task_order());
|
||
|
|
// Make sure enqueue order is strictly increasing for immediate queues and
|
||
|
|
// monotonically increasing for delayed queues.
|
||
|
|
DCHECK(was_empty || tasks_.back().enqueue_order() < task.enqueue_order() ||
|
||
|
|
(queue_type_ == QueueType::kDelayed &&
|
||
|
|
tasks_.back().enqueue_order() == task.enqueue_order()));
|
||
|
|
|
||
|
|
// Amortized O(1).
|
||
|
|
tasks_.push_back(std::move(task));
|
||
|
|
|
||
|
|
if (!was_empty)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// If we hit the fence, pretend to WorkQueueSets that we're empty.
|
||
|
|
if (work_queue_sets_ && !BlockedByFence())
|
||
|
|
work_queue_sets_->OnTaskPushedToEmptyQueue(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
WorkQueue::TaskPusher::TaskPusher(WorkQueue* work_queue)
|
||
|
|
: work_queue_(work_queue), was_empty_(work_queue->Empty()) {}
|
||
|
|
|
||
|
|
WorkQueue::TaskPusher::TaskPusher(TaskPusher&& other)
|
||
|
|
: work_queue_(other.work_queue_), was_empty_(other.was_empty_) {
|
||
|
|
other.work_queue_ = nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::TaskPusher::Push(Task task) {
|
||
|
|
DCHECK(work_queue_);
|
||
|
|
|
||
|
|
#ifndef NDEBUG
|
||
|
|
DCHECK(task.enqueue_order_set());
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Make sure the task order is strictly increasing.
|
||
|
|
DCHECK(work_queue_->tasks_.empty() ||
|
||
|
|
work_queue_->tasks_.back().task_order() < task.task_order());
|
||
|
|
// Make sure enqueue order is strictly increasing for immediate queues and
|
||
|
|
// monotonically increasing for delayed queues.
|
||
|
|
DCHECK(work_queue_->tasks_.empty() ||
|
||
|
|
work_queue_->tasks_.back().enqueue_order() < task.enqueue_order() ||
|
||
|
|
(work_queue_->queue_type_ == QueueType::kDelayed &&
|
||
|
|
work_queue_->tasks_.back().enqueue_order() == task.enqueue_order()));
|
||
|
|
|
||
|
|
// Amortized O(1).
|
||
|
|
work_queue_->tasks_.push_back(std::move(task));
|
||
|
|
}
|
||
|
|
|
||
|
|
WorkQueue::TaskPusher::~TaskPusher() {
|
||
|
|
// If |work_queue_| became non empty and it isn't blocked by a fence then we
|
||
|
|
// must notify |work_queue_->work_queue_sets_|.
|
||
|
|
if (was_empty_ && work_queue_ && !work_queue_->Empty() &&
|
||
|
|
work_queue_->work_queue_sets_ && !work_queue_->BlockedByFence()) {
|
||
|
|
work_queue_->work_queue_sets_->OnTaskPushedToEmptyQueue(work_queue_);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
WorkQueue::TaskPusher WorkQueue::CreateTaskPusher() {
|
||
|
|
return TaskPusher(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::PushNonNestableTaskToFront(Task task) {
|
||
|
|
DCHECK(task.nestable == Nestable::kNonNestable);
|
||
|
|
|
||
|
|
bool was_empty = tasks_.empty();
|
||
|
|
bool was_blocked = BlockedByFence();
|
||
|
|
#ifndef NDEBUG
|
||
|
|
DCHECK(task.enqueue_order_set());
|
||
|
|
#endif
|
||
|
|
|
||
|
|
if (!was_empty) {
|
||
|
|
// Make sure the task order is strictly increasing.
|
||
|
|
DCHECK(task.task_order() < tasks_.front().task_order())
|
||
|
|
<< task_queue_->GetName() << " : " << work_queue_sets_->GetName()
|
||
|
|
<< " : " << name_;
|
||
|
|
// Make sure the enqueue order is strictly increasing for immediate queues
|
||
|
|
// and monotonically increasing for delayed queues.
|
||
|
|
DCHECK(task.enqueue_order() < tasks_.front().enqueue_order() ||
|
||
|
|
(queue_type_ == QueueType::kDelayed &&
|
||
|
|
task.enqueue_order() == tasks_.front().enqueue_order()))
|
||
|
|
<< task_queue_->GetName() << " : " << work_queue_sets_->GetName()
|
||
|
|
<< " : " << name_;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Amortized O(1).
|
||
|
|
tasks_.push_front(std::move(task));
|
||
|
|
|
||
|
|
if (!work_queue_sets_)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Pretend to WorkQueueSets that nothing has changed if we're blocked.
|
||
|
|
if (BlockedByFence())
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Pushing task to front may unblock the fence.
|
||
|
|
if (was_empty || was_blocked) {
|
||
|
|
work_queue_sets_->OnTaskPushedToEmptyQueue(this);
|
||
|
|
} else {
|
||
|
|
work_queue_sets_->OnQueuesFrontTaskChanged(this);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::TakeImmediateIncomingQueueTasks() {
|
||
|
|
DCHECK(tasks_.empty());
|
||
|
|
|
||
|
|
task_queue_->TakeImmediateIncomingQueueTasks(&tasks_);
|
||
|
|
if (tasks_.empty())
|
||
|
|
return;
|
||
|
|
|
||
|
|
// If we hit the fence, pretend to WorkQueueSets that we're empty.
|
||
|
|
if (work_queue_sets_ && !BlockedByFence())
|
||
|
|
work_queue_sets_->OnTaskPushedToEmptyQueue(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
Task WorkQueue::TakeTaskFromWorkQueue() {
|
||
|
|
DCHECK(work_queue_sets_);
|
||
|
|
DCHECK(!tasks_.empty());
|
||
|
|
|
||
|
|
Task pending_task = std::move(tasks_.front());
|
||
|
|
tasks_.pop_front();
|
||
|
|
// NB immediate tasks have a different pipeline to delayed ones.
|
||
|
|
if (tasks_.empty()) {
|
||
|
|
// NB delayed tasks are inserted via Push, no don't need to reload those.
|
||
|
|
if (queue_type_ == QueueType::kImmediate) {
|
||
|
|
// Short-circuit the queue reload so that OnPopMinQueueInSet does the
|
||
|
|
// right thing.
|
||
|
|
task_queue_->TakeImmediateIncomingQueueTasks(&tasks_);
|
||
|
|
}
|
||
|
|
// Since the queue is empty, now is a good time to consider reducing it's
|
||
|
|
// capacity if we're wasting memory.
|
||
|
|
tasks_.MaybeShrinkQueue();
|
||
|
|
}
|
||
|
|
|
||
|
|
DCHECK(work_queue_sets_);
|
||
|
|
#if DCHECK_IS_ON()
|
||
|
|
// If diagnostics are on it's possible task queues are being selected at
|
||
|
|
// random so we can't use the (slightly) more efficient OnPopMinQueueInSet.
|
||
|
|
work_queue_sets_->OnQueuesFrontTaskChanged(this);
|
||
|
|
#else
|
||
|
|
// OnPopMinQueueInSet calls GetFrontTaskOrder which checks
|
||
|
|
// BlockedByFence() so we don't need to here.
|
||
|
|
work_queue_sets_->OnPopMinQueueInSet(this);
|
||
|
|
#endif
|
||
|
|
task_queue_->TraceQueueSize();
|
||
|
|
return pending_task;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool WorkQueue::RemoveAllCanceledTasksFromFront() {
|
||
|
|
if (!work_queue_sets_) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Since task destructors could have a side-effect of deleting this task queue
|
||
|
|
// we move cancelled tasks into a temporary container which can be emptied
|
||
|
|
// without accessing |this|.
|
||
|
|
StackVector<Task, 8> tasks_to_delete;
|
||
|
|
|
||
|
|
while (!tasks_.empty()) {
|
||
|
|
const auto& pending_task = tasks_.front();
|
||
|
|
if (pending_task.task && !pending_task.IsCanceled())
|
||
|
|
break;
|
||
|
|
tasks_to_delete->push_back(std::move(tasks_.front()));
|
||
|
|
tasks_.pop_front();
|
||
|
|
}
|
||
|
|
if (!tasks_to_delete->empty()) {
|
||
|
|
if (tasks_.empty()) {
|
||
|
|
// NB delayed tasks are inserted via Push, no don't need to reload those.
|
||
|
|
if (queue_type_ == QueueType::kImmediate) {
|
||
|
|
// Short-circuit the queue reload so that OnPopMinQueueInSet does the
|
||
|
|
// right thing.
|
||
|
|
task_queue_->TakeImmediateIncomingQueueTasks(&tasks_);
|
||
|
|
}
|
||
|
|
// Since the queue is empty, now is a good time to consider reducing it's
|
||
|
|
// capacity if we're wasting memory.
|
||
|
|
tasks_.MaybeShrinkQueue();
|
||
|
|
}
|
||
|
|
// If we have a valid |heap_handle_| (i.e. we're not blocked by a fence or
|
||
|
|
// disabled) then |work_queue_sets_| needs to be told.
|
||
|
|
if (heap_handle_.IsValid())
|
||
|
|
work_queue_sets_->OnQueuesFrontTaskChanged(this);
|
||
|
|
task_queue_->TraceQueueSize();
|
||
|
|
}
|
||
|
|
return !tasks_to_delete->empty();
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::AssignToWorkQueueSets(WorkQueueSets* work_queue_sets) {
|
||
|
|
work_queue_sets_ = work_queue_sets;
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::AssignSetIndex(size_t work_queue_set_index) {
|
||
|
|
work_queue_set_index_ = work_queue_set_index;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool WorkQueue::InsertFenceImpl(Fence fence) {
|
||
|
|
DCHECK(!fence_ || fence.task_order() >= fence_->task_order() ||
|
||
|
|
fence.IsBlockingFence());
|
||
|
|
bool was_blocked_by_fence = BlockedByFence();
|
||
|
|
fence_ = fence;
|
||
|
|
return was_blocked_by_fence;
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::InsertFenceSilently(Fence fence) {
|
||
|
|
// Ensure that there is no fence present or a new one blocks queue completely.
|
||
|
|
DCHECK(!fence_ || fence_->IsBlockingFence());
|
||
|
|
InsertFenceImpl(fence);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool WorkQueue::InsertFence(Fence fence) {
|
||
|
|
bool was_blocked_by_fence = InsertFenceImpl(fence);
|
||
|
|
if (!work_queue_sets_)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Moving the fence forward may unblock some tasks.
|
||
|
|
if (!tasks_.empty() && was_blocked_by_fence && !BlockedByFence()) {
|
||
|
|
work_queue_sets_->OnTaskPushedToEmptyQueue(this);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// Fence insertion may have blocked all tasks in this work queue.
|
||
|
|
if (BlockedByFence())
|
||
|
|
work_queue_sets_->OnQueueBlocked(this);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool WorkQueue::RemoveFence() {
|
||
|
|
bool was_blocked_by_fence = BlockedByFence();
|
||
|
|
fence_ = absl::nullopt;
|
||
|
|
if (work_queue_sets_ && !tasks_.empty() && was_blocked_by_fence) {
|
||
|
|
work_queue_sets_->OnTaskPushedToEmptyQueue(this);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::MaybeShrinkQueue() {
|
||
|
|
tasks_.MaybeShrinkQueue();
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::PopTaskForTesting() {
|
||
|
|
if (tasks_.empty())
|
||
|
|
return;
|
||
|
|
tasks_.pop_front();
|
||
|
|
}
|
||
|
|
|
||
|
|
void WorkQueue::CollectTasksOlderThan(TaskOrder reference,
|
||
|
|
std::vector<const Task*>* result) const {
|
||
|
|
for (const Task& task : tasks_) {
|
||
|
|
if (task.task_order() >= reference)
|
||
|
|
break;
|
||
|
|
|
||
|
|
result->push_back(&task);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace internal
|
||
|
|
} // namespace sequence_manager
|
||
|
|
} // namespace base
|