352 lines
11 KiB
C++
352 lines
11 KiB
C++
// Copyright 2016 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/thread_pool/sequence.h"
|
|
|
|
#include <utility>
|
|
|
|
#include "base/check.h"
|
|
#include "base/critical_closure.h"
|
|
#include "base/feature_list.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/task/task_features.h"
|
|
#include "base/time/time.h"
|
|
|
|
namespace base {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
// Asserts that a lock is acquired and annotates the scope such that
|
|
// base/thread_annotations.h can recognize that the lock is acquired.
|
|
class SCOPED_LOCKABLE AnnotateLockAcquired {
|
|
public:
|
|
explicit AnnotateLockAcquired(const CheckedLock& lock)
|
|
EXCLUSIVE_LOCK_FUNCTION(lock)
|
|
: acquired_lock_(lock) {
|
|
acquired_lock_->AssertAcquired();
|
|
}
|
|
|
|
~AnnotateLockAcquired() UNLOCK_FUNCTION() {
|
|
acquired_lock_->AssertAcquired();
|
|
}
|
|
|
|
private:
|
|
const raw_ref<const CheckedLock> acquired_lock_;
|
|
};
|
|
|
|
void MaybeMakeCriticalClosure(TaskShutdownBehavior shutdown_behavior,
|
|
Task& task) {
|
|
switch (shutdown_behavior) {
|
|
case TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN:
|
|
// Nothing to do.
|
|
break;
|
|
case TaskShutdownBehavior::SKIP_ON_SHUTDOWN:
|
|
// MakeCriticalClosure() is arguably useful for SKIP_ON_SHUTDOWN, possibly
|
|
// in combination with is_immediate=false. However, SKIP_ON_SHUTDOWN is
|
|
// the default and hence the theoretical benefits don't warrant the
|
|
// performance implications.
|
|
break;
|
|
case TaskShutdownBehavior::BLOCK_SHUTDOWN:
|
|
task.task =
|
|
MakeCriticalClosure(task.posted_from, std::move(task.task),
|
|
/*is_immediate=*/task.delayed_run_time.is_null());
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Sequence::Transaction::Transaction(Sequence* sequence)
|
|
: TaskSource::Transaction(sequence) {}
|
|
|
|
Sequence::Transaction::Transaction(Sequence::Transaction&& other) = default;
|
|
|
|
Sequence::Transaction::~Transaction() = default;
|
|
|
|
bool Sequence::Transaction::WillPushImmediateTask() {
|
|
// In a Transaction.
|
|
AnnotateLockAcquired annotate(sequence()->lock_);
|
|
|
|
bool was_immediate =
|
|
sequence()->is_immediate_.exchange(true, std::memory_order_relaxed);
|
|
return !was_immediate;
|
|
}
|
|
|
|
void Sequence::Transaction::PushImmediateTask(Task task) {
|
|
// In a Transaction.
|
|
AnnotateLockAcquired annotate(sequence()->lock_);
|
|
|
|
// Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
|
|
// for details.
|
|
CHECK(task.task);
|
|
DCHECK(!task.queue_time.is_null());
|
|
DCHECK(sequence()->is_immediate_.load(std::memory_order_relaxed));
|
|
|
|
bool was_unretained = sequence()->IsEmpty() && !sequence()->has_worker_;
|
|
bool queue_was_empty = sequence()->queue_.empty();
|
|
|
|
MaybeMakeCriticalClosure(sequence()->traits_.shutdown_behavior(), task);
|
|
|
|
sequence()->queue_.push(std::move(task));
|
|
|
|
if (queue_was_empty)
|
|
sequence()->UpdateReadyTimes();
|
|
|
|
// AddRef() matched by manual Release() when the sequence has no more tasks
|
|
// to run (in DidProcessTask() or Clear()).
|
|
if (was_unretained && sequence()->task_runner())
|
|
sequence()->task_runner()->AddRef();
|
|
}
|
|
|
|
bool Sequence::Transaction::PushDelayedTask(Task task) {
|
|
// In a Transaction.
|
|
AnnotateLockAcquired annotate(sequence()->lock_);
|
|
|
|
// Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
|
|
// for details.
|
|
CHECK(task.task);
|
|
DCHECK(!task.queue_time.is_null());
|
|
DCHECK(!task.delayed_run_time.is_null());
|
|
|
|
bool top_will_change = sequence()->DelayedSortKeyWillChange(task);
|
|
bool was_empty = sequence()->IsEmpty();
|
|
|
|
MaybeMakeCriticalClosure(sequence()->traits_.shutdown_behavior(), task);
|
|
|
|
sequence()->delayed_queue_.insert(std::move(task));
|
|
|
|
if (sequence()->queue_.empty())
|
|
sequence()->UpdateReadyTimes();
|
|
|
|
// AddRef() matched by manual Release() when the sequence has no more tasks
|
|
// to run (in DidProcessTask() or Clear()).
|
|
if (was_empty && !sequence()->has_worker_ && sequence()->task_runner())
|
|
sequence()->task_runner()->AddRef();
|
|
|
|
return top_will_change;
|
|
}
|
|
|
|
// Delayed tasks are ordered by latest_delayed_run_time(). The top task may
|
|
// not be the first task eligible to run, but tasks will always become ripe
|
|
// before their latest_delayed_run_time().
|
|
bool Sequence::DelayedTaskGreater::operator()(const Task& lhs,
|
|
const Task& rhs) const {
|
|
TimeTicks lhs_latest_delayed_run_time = lhs.latest_delayed_run_time();
|
|
TimeTicks rhs_latest_delayed_run_time = rhs.latest_delayed_run_time();
|
|
return std::tie(lhs_latest_delayed_run_time, lhs.sequence_num) >
|
|
std::tie(rhs_latest_delayed_run_time, rhs.sequence_num);
|
|
}
|
|
|
|
TaskSource::RunStatus Sequence::WillRunTask() {
|
|
// There should never be a second call to WillRunTask() before DidProcessTask
|
|
// since the RunStatus is always marked a saturated.
|
|
DCHECK(!has_worker_);
|
|
|
|
// It's ok to access |has_worker_| outside of a Transaction since
|
|
// WillRunTask() is externally synchronized, always called in sequence with
|
|
// TakeTask() and DidProcessTask() and only called if HasReadyTasks(), which
|
|
// means it won't race with Push[Immediate/Delayed]Task().
|
|
has_worker_ = true;
|
|
return RunStatus::kAllowedSaturated;
|
|
}
|
|
|
|
bool Sequence::OnBecomeReady() {
|
|
DCHECK(!has_worker_);
|
|
// std::memory_order_relaxed is sufficient because no other state is
|
|
// synchronized with |is_immediate_| outside of |lock_|.
|
|
return !is_immediate_.exchange(true, std::memory_order_relaxed);
|
|
}
|
|
|
|
size_t Sequence::GetRemainingConcurrency() const {
|
|
return 1;
|
|
}
|
|
|
|
Task Sequence::TakeNextImmediateTask() {
|
|
Task next_task = std::move(queue_.front());
|
|
queue_.pop();
|
|
return next_task;
|
|
}
|
|
|
|
Task Sequence::TakeEarliestTask() {
|
|
if (queue_.empty())
|
|
return delayed_queue_.take_top();
|
|
|
|
if (delayed_queue_.empty())
|
|
return TakeNextImmediateTask();
|
|
|
|
// Both queues contain at least a task. Decide from which one the task should
|
|
// be taken.
|
|
if (queue_.front().queue_time <=
|
|
delayed_queue_.top().latest_delayed_run_time())
|
|
return TakeNextImmediateTask();
|
|
|
|
return delayed_queue_.take_top();
|
|
}
|
|
|
|
void Sequence::UpdateReadyTimes() {
|
|
DCHECK(!IsEmpty());
|
|
if (queue_.empty()) {
|
|
latest_ready_time_.store(delayed_queue_.top().latest_delayed_run_time(),
|
|
std::memory_order_relaxed);
|
|
earliest_ready_time_.store(delayed_queue_.top().earliest_delayed_run_time(),
|
|
std::memory_order_relaxed);
|
|
return;
|
|
}
|
|
|
|
if (delayed_queue_.empty()) {
|
|
latest_ready_time_.store(queue_.front().queue_time,
|
|
std::memory_order_relaxed);
|
|
} else {
|
|
latest_ready_time_.store(
|
|
std::min(queue_.front().queue_time,
|
|
delayed_queue_.top().latest_delayed_run_time()),
|
|
std::memory_order_relaxed);
|
|
}
|
|
earliest_ready_time_.store(TimeTicks(), std::memory_order_relaxed);
|
|
}
|
|
|
|
Task Sequence::TakeTask(TaskSource::Transaction* transaction) {
|
|
CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
|
|
AnnotateLockAcquired annotate(lock_);
|
|
|
|
DCHECK(has_worker_);
|
|
DCHECK(is_immediate_.load(std::memory_order_relaxed));
|
|
DCHECK(!queue_.empty() || !delayed_queue_.empty());
|
|
|
|
auto next_task = TakeEarliestTask();
|
|
|
|
if (!IsEmpty())
|
|
UpdateReadyTimes();
|
|
|
|
return next_task;
|
|
}
|
|
|
|
bool Sequence::DidProcessTask(TaskSource::Transaction* transaction) {
|
|
CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
|
|
AnnotateLockAcquired annotate(lock_);
|
|
|
|
// There should never be a call to DidProcessTask without an associated
|
|
// WillRunTask().
|
|
DCHECK(has_worker_);
|
|
has_worker_ = false;
|
|
|
|
// See comment on TaskSource::task_runner_ for lifetime management details.
|
|
if (IsEmpty()) {
|
|
is_immediate_.store(false, std::memory_order_relaxed);
|
|
ReleaseTaskRunner();
|
|
return false;
|
|
}
|
|
|
|
// Let the caller re-enqueue this non-empty Sequence regardless of
|
|
// |run_result| so it can continue churning through this Sequence's tasks and
|
|
// skip/delete them in the proper scope.
|
|
return true;
|
|
}
|
|
|
|
bool Sequence::WillReEnqueue(TimeTicks now,
|
|
TaskSource::Transaction* transaction) {
|
|
CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
|
|
AnnotateLockAcquired annotate(lock_);
|
|
|
|
// This should always be called from a worker thread and it will be
|
|
// called after DidProcessTask().
|
|
DCHECK(is_immediate_.load(std::memory_order_relaxed));
|
|
|
|
bool has_ready_tasks = HasReadyTasks(now);
|
|
if (!has_ready_tasks)
|
|
is_immediate_.store(false, std::memory_order_relaxed);
|
|
|
|
return has_ready_tasks;
|
|
}
|
|
|
|
bool Sequence::DelayedSortKeyWillChange(const Task& delayed_task) const {
|
|
// If sequence has already been picked up by a worker or moved, no need to
|
|
// proceed further here.
|
|
if (is_immediate_.load(std::memory_order_relaxed)) {
|
|
return false;
|
|
}
|
|
|
|
if (IsEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
return delayed_task.latest_delayed_run_time() <
|
|
delayed_queue_.top().latest_delayed_run_time();
|
|
}
|
|
|
|
bool Sequence::HasReadyTasks(TimeTicks now) const {
|
|
return now >= TS_UNCHECKED_READ(earliest_ready_time_)
|
|
.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
bool Sequence::HasImmediateTasks() const {
|
|
return !queue_.empty();
|
|
}
|
|
|
|
TaskSourceSortKey Sequence::GetSortKey() const {
|
|
return TaskSourceSortKey(
|
|
priority_racy(),
|
|
TS_UNCHECKED_READ(latest_ready_time_).load(std::memory_order_relaxed));
|
|
}
|
|
|
|
TimeTicks Sequence::GetDelayedSortKey() const {
|
|
return TS_UNCHECKED_READ(latest_ready_time_).load(std::memory_order_relaxed);
|
|
}
|
|
|
|
Task Sequence::Clear(TaskSource::Transaction* transaction) {
|
|
CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
|
|
AnnotateLockAcquired annotate(lock_);
|
|
|
|
// See comment on TaskSource::task_runner_ for lifetime management details.
|
|
if (!IsEmpty() && !has_worker_) {
|
|
ReleaseTaskRunner();
|
|
}
|
|
|
|
return Task(
|
|
FROM_HERE,
|
|
base::BindOnce(
|
|
[](base::queue<Task> queue,
|
|
base::IntrusiveHeap<Task, DelayedTaskGreater> delayed_queue) {
|
|
while (!queue.empty())
|
|
queue.pop();
|
|
|
|
while (!delayed_queue.empty())
|
|
delayed_queue.pop();
|
|
},
|
|
std::move(queue_), std::move(delayed_queue_)),
|
|
TimeTicks(), TimeDelta());
|
|
}
|
|
|
|
void Sequence::ReleaseTaskRunner() {
|
|
if (!task_runner())
|
|
return;
|
|
// No member access after this point, releasing |task_runner()| might delete
|
|
// |this|.
|
|
task_runner()->Release();
|
|
}
|
|
|
|
Sequence::Sequence(const TaskTraits& traits,
|
|
TaskRunner* task_runner,
|
|
TaskSourceExecutionMode execution_mode)
|
|
: TaskSource(traits, task_runner, execution_mode) {}
|
|
|
|
Sequence::~Sequence() = default;
|
|
|
|
Sequence::Transaction Sequence::BeginTransaction() {
|
|
return Transaction(this);
|
|
}
|
|
|
|
ExecutionEnvironment Sequence::GetExecutionEnvironment() {
|
|
return {token_, &sequence_local_storage_};
|
|
}
|
|
|
|
bool Sequence::IsEmpty() const {
|
|
return queue_.empty() && delayed_queue_.empty();
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace base
|