Import chromium-132.0.6834.79

This commit is contained in:
importer
2025-01-14 19:56:51 +08:00
committed by klzgrad
commit a065c28aa3
19621 changed files with 3907669 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
monorail: {
component: "Internals>TaskScheduling"
}
team_email: "scheduler-dev@chromium.org"
buganizer_public: {
component_id: 1456866
}

8
src/base/task/OWNERS Normal file
View File

@@ -0,0 +1,8 @@
# For public facing APIs, for impl details of ThreadPool and SequenceManager see
# subfolder OWNERS.
altimin@chromium.org
carlscab@google.com
etiennep@chromium.org
fdoray@chromium.org
gab@chromium.org

16
src/base/task/README.md Normal file
View File

@@ -0,0 +1,16 @@
This directory has the following layout:
- base/task/: public APIs for posting tasks and managing task queues.
- base/task/thread_pool/: implementation of the ThreadPool.
- base/task/sequence_manager/: implementation of the SequenceManager.
- base/task/common/: implementation details shared by ThreadPool and
SequenceManager.
Apart from embedders explicitly managing a ThreadPoolInstance and/or
SequenceManager instance(s) for their process/threads, the vast majority of
users should only need APIs in base/task/.
Documentation:
* [Threading and tasks](/docs/threading_and_tasks.md)
* [Callbacks](/docs/callback.md)
* [Vision for future API changes](https://docs.google.com/document/d/1pySz2xeJ6kLlbzDnS2jqAC1F8T_6pLEV8pgaMfURXAw/edit)

View File

@@ -0,0 +1,128 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_BIND_POST_TASK_H_
#define BASE_TASK_BIND_POST_TASK_H_
#include <memory>
#include <type_traits>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/task/bind_post_task_internal.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
// BindPostTask() is a helper function for binding a OnceCallback or
// RepeatingCallback to a task runner. BindPostTask(task_runner, callback)
// returns a task runner bound callback with an identical type to |callback|.
// The returned callback will take the same arguments as the input |callback|.
// Invoking Run() on the returned callback will post a task to run |callback| on
// target |task_runner| with the provided arguments.
//
// This is typically used when a callback must be invoked on a specific task
// runner but is provided as a result callback to a function that runs
// asynchronously on a different task runner.
//
// Example:
// // |result_cb| can only be safely run on |my_task_runner|.
// auto result_cb = BindOnce(&Foo::ReceiveReply, foo);
// // Note that even if |returned_cb| is never run |result_cb| will attempt
// // to be destroyed on |my_task_runner|.
// auto returned_cb = BindPostTask(my_task_runner, std::move(result_cb));
// // RunAsyncTask() will run the provided callback upon completion.
// other_task_runner->PostTask(FROM_HERE,
// BindOnce(&RunAsyncTask,
// std::move(returned_cb)));
//
// If the example provided |result_cb| to RunAsyncTask() then |result_cb| would
// run unsafely on |other_task_runner|. Instead RunAsyncTask() will run
// |returned_cb| which will post a task to |current_task_runner| before running
// |result_cb| safely.
//
// An alternative approach in the example above is to change RunAsyncTask() to
// also take a task runner as an argument and have RunAsyncTask() post the task.
// For cases where that isn't desirable BindPostTask() provides a convenient
// alternative.
//
// The input |callback| will always attempt to be destroyed on the target task
// runner. Even if the returned callback is never invoked, a task will be posted
// to destroy the input |callback|. However, if the target task runner has
// shutdown this is no longer possible. PostTask() will return false and the
// callback will be destroyed immediately on the current thread.
//
// The input |callback| must have a void return type to be compatible with
// PostTask(). If you want to drop the callback return value then use
// base::IgnoreResult(&Func) when creating the input |callback|.
namespace base {
// Creates a OnceCallback that will run |callback| on |task_runner|. If the
// returned callback is destroyed without being run then |callback| will be
// be destroyed on |task_runner|.
template <typename ReturnType, typename... Args>
requires std::is_void_v<ReturnType>
OnceCallback<void(Args...)> BindPostTask(
scoped_refptr<TaskRunner> task_runner,
OnceCallback<ReturnType(Args...)> callback,
const Location& location = FROM_HERE) {
using Helper = internal::BindPostTaskTrampoline<OnceCallback<void(Args...)>>;
return base::BindOnce(
&Helper::template Run<Args...>,
std::make_unique<Helper>(std::move(task_runner), location,
std::move(callback)));
}
// Creates a RepeatingCallback that will run |callback| on |task_runner|. When
// the returned callback is destroyed a task will be posted to destroy the input
// |callback| on |task_runner|.
template <typename ReturnType, typename... Args>
requires std::is_void_v<ReturnType>
RepeatingCallback<void(Args...)> BindPostTask(
scoped_refptr<TaskRunner> task_runner,
RepeatingCallback<ReturnType(Args...)> callback,
const Location& location = FROM_HERE) {
using Helper =
internal::BindPostTaskTrampoline<RepeatingCallback<void(Args...)>>;
return base::BindRepeating(
&Helper::template Run<Args...>,
std::make_unique<Helper>(std::move(task_runner), location,
std::move(callback)));
}
// Creates a OnceCallback or RepeatingCallback that will run the `callback` on
// the default SequencedTaskRunner for the current sequence, i.e.
// `SequencedTaskRunner::GetCurrentDefault()`.
// Notes:
// - Please prefer using `base::SequenceBound<T>` if applicable.
// - Please consider using `base::PostTaskAndReplyWithResult()` instead where
// appropriate.
// - Please consider using an explicit task runner.
// - Only use this helper as a last resort if none of the above apply.
template <typename ReturnType, typename... Args>
requires std::is_void_v<ReturnType>
OnceCallback<void(Args...)> BindPostTaskToCurrentDefault(
OnceCallback<ReturnType(Args...)> callback,
const Location& location = FROM_HERE) {
return BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
std::move(callback), location);
}
template <typename ReturnType, typename... Args>
requires std::is_void_v<ReturnType>
RepeatingCallback<void(Args...)> BindPostTaskToCurrentDefault(
RepeatingCallback<ReturnType(Args...)> callback,
const Location& location = FROM_HERE) {
return BindPostTask(SequencedTaskRunner::GetCurrentDefault(),
std::move(callback), location);
}
} // namespace base
#endif // BASE_TASK_BIND_POST_TASK_H_

View File

@@ -0,0 +1,106 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_BIND_POST_TASK_INTERNAL_H_
#define BASE_TASK_BIND_POST_TASK_INTERNAL_H_
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/task/task_runner.h"
#include "base/task/thread_pool/thread_pool_instance.h"
namespace base {
namespace internal {
// Helper class to ensure that the input callback is always invoked and
// destroyed on the provided task runner.
template <typename CallbackType>
class BindPostTaskTrampoline {
public:
BindPostTaskTrampoline(scoped_refptr<TaskRunner> task_runner,
const Location& location,
CallbackType callback)
: task_runner_(std::move(task_runner)),
location_(location),
callback_(std::move(callback)) {
DCHECK(task_runner_);
// Crash immediately instead of when trying to Run() `callback_` on the
// destination `task_runner_`.
CHECK(callback_);
}
BindPostTaskTrampoline(const BindPostTaskTrampoline& other) = delete;
BindPostTaskTrampoline& operator=(const BindPostTaskTrampoline& other) =
delete;
~BindPostTaskTrampoline() {
if (callback_) {
// Allow this task to be leaked on shutdown even if `task_runner_` has the
// TaskShutdownBehaviour::BLOCK_SHUTDOWN trait. Without `fizzler`, such a
// task runner would DCHECK when posting to `task_runner_` after shutdown.
// Ignore this DCHECK as the poster isn't in control when its Callback is
// destroyed late into shutdown. Ref. crbug.com/1375270.
base::ThreadPoolInstance::ScopedFizzleBlockShutdownTasks fizzler;
// Post a task to ensure that `callback_` is destroyed on `task_runner_`.
// The callback's BindState may own an object that isn't threadsafe and is
// unsafe to destroy on a different task runner.
//
// Note that while this guarantees `callback_` will be destroyed when the
// posted task runs, it doesn't guarantee the ref-counted BindState is
// destroyed at the same time. If the callback was copied before being
// passed to BindPostTaskTrampoline then the BindState can outlive
// `callback_`, so the user must ensure any other copies of the callback
// are also destroyed on the correct task runner.
task_runner_->PostTask(
location_,
base::BindOnce(&DestroyCallbackOnTaskRunner, std::move(callback_)));
}
}
template <typename... Args>
void Run(Args... args) {
// If CallbackType is a OnceCallback then GetClosure() consumes `callback_`.
task_runner_->PostTask(location_,
GetClosure(&callback_, std::forward<Args>(args)...));
}
private:
static OnceClosure GetClosure(OnceClosure* callback) {
// `callback` is already a closure, no need to call BindOnce().
return std::move(*callback);
}
template <typename... Args>
static OnceClosure GetClosure(OnceCallback<void(Args...)>* callback,
Args&&... args) {
return base::BindOnce(std::move(*callback), std::forward<Args>(args)...);
}
static OnceClosure GetClosure(RepeatingClosure* callback) {
// `callback` is already a closure, no need to call BindOnce().
return *callback;
}
template <typename... Args>
static OnceClosure GetClosure(RepeatingCallback<void(Args...)>* callback,
Args&&... args) {
return base::BindOnce(*callback, std::forward<Args>(args)...);
}
static void DestroyCallbackOnTaskRunner(CallbackType callback) {}
const scoped_refptr<TaskRunner> task_runner_;
const Location location_;
CallbackType callback_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_BIND_POST_TASK_INTERNAL_H_

View File

@@ -0,0 +1,55 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is a "No Compile Test" suite.
// http://dev.chromium.org/developers/testing/no-compile-tests
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
namespace base {
// In general, compilers only print diagnostics for errors once when initially
// instantiating the template. Subsequent uses of the same template will not
// print out any diagnostics.
//
// Using multiple functions that return different types ensures that each
// BindPostTask() call creates a new instantiation.
int ReturnsInt();
double ReturnsDouble();
void OnceCallbackWithNonVoidReturnType1() {
{
OnceCallback<int()> cb = BindOnce(&ReturnsInt);
auto post_cb = BindPostTask(SequencedTaskRunner::GetCurrentDefault(), std::move(cb)); // expected-error@base/task/bind_post_task_nocompile.nc:* {{no matching function for call to 'BindPostTask'}}
// expected-note@base/task/bind_post_task.h:* {{because 'std::is_void_v<int>' evaluated to false}}
}
{
OnceCallback<double()> cb = BindOnce(&ReturnsDouble);
auto post_cb = BindPostTaskToCurrentDefault(std::move(cb)); // expected-error@base/task/bind_post_task_nocompile.nc:* {{no matching function for call to 'BindPostTaskToCurrentDefault'}}
} // expected-note@base/task/bind_post_task.h:* {{because 'std::is_void_v<double>' evaluated to false}}
}
void RepeatingCallbackWithNonVoidReturnType() {
{
RepeatingCallback<int()> cb = BindRepeating(&ReturnsInt);
auto post_cb = BindPostTask(SequencedTaskRunner::GetCurrentDefault(), std::move(cb)); // expected-error@base/task/bind_post_task_nocompile.nc:* {{no matching function for call to 'BindPostTask'}}
// expected-note@base/task/bind_post_task.h:* {{because 'std::is_void_v<int>' evaluated to false}}
}
{
RepeatingCallback<double()> cb = BindRepeating(&ReturnsDouble);
auto post_cb = BindPostTaskToCurrentDefault(std::move(cb)); // expected-error@base/task/bind_post_task_nocompile.nc:* {{no matching function for call to 'BindPostTaskToCurrentDefault'}}
// expected-note@base/task/bind_post_task.h:* {{because 'std::is_void_v<double>' evaluated to false}}
}
}
} // namespace base

View File

@@ -0,0 +1,188 @@
// Copyright 2014 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/cancelable_task_tracker.h"
#include <stddef.h>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
namespace base {
namespace {
void RunOrPostToTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner,
OnceClosure closure) {
if (task_runner->RunsTasksInCurrentSequence())
std::move(closure).Run();
else
task_runner->PostTask(FROM_HERE, std::move(closure));
}
} // namespace
// static
const CancelableTaskTracker::TaskId CancelableTaskTracker::kBadTaskId = 0;
CancelableTaskTracker::CancelableTaskTracker() {
weak_this_ = weak_factory_.GetWeakPtr();
}
CancelableTaskTracker::~CancelableTaskTracker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TryCancelAll();
}
CancelableTaskTracker::TaskId CancelableTaskTracker::PostTask(
TaskRunner* task_runner,
const Location& from_here,
OnceClosure task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(weak_this_);
return PostTaskAndReply(task_runner, from_here, std::move(task), DoNothing());
}
CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply(
TaskRunner* task_runner,
const Location& from_here,
OnceClosure task,
OnceClosure reply) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(weak_this_);
// We need a SequencedTaskRunner::CurrentDefaultHandle to run |reply|.
DCHECK(SequencedTaskRunner::HasCurrentDefault());
auto flag = MakeRefCounted<TaskCancellationFlag>();
TaskId id = next_id_;
next_id_++; // int64_t is big enough that we ignore the potential overflow.
// Unretained(this) is safe because |flag| will have been set to the
// "canceled" state after |this| is deleted.
OnceClosure untrack_closure =
BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id);
bool success = task_runner->PostTaskAndReply(
from_here, BindOnce(&RunIfNotCanceled, flag, std::move(task)),
BindOnce(&RunThenUntrackIfNotCanceled, flag, std::move(reply),
std::move(untrack_closure)));
if (!success)
return kBadTaskId;
Track(id, std::move(flag));
return id;
}
CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId(
IsCanceledCallback* is_canceled_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(SequencedTaskRunner::HasCurrentDefault());
TaskId id = next_id_;
next_id_++; // int64_t is big enough that we ignore the potential overflow.
auto flag = MakeRefCounted<TaskCancellationFlag>();
// Unretained(this) is safe because |flag| will have been set to the
// "canceled" state after |this| is deleted.
OnceClosure untrack_closure =
BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id);
// Will always run |untrack_closure| on current sequence.
ScopedClosureRunner untrack_runner(
BindOnce(&RunOrPostToTaskRunner, SequencedTaskRunner::GetCurrentDefault(),
BindOnce(&RunIfNotCanceled, flag, std::move(untrack_closure))));
*is_canceled_cb = BindRepeating(&IsCanceled, flag, std::move(untrack_runner));
Track(id, std::move(flag));
return id;
}
void CancelableTaskTracker::TryCancel(TaskId id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const auto it = task_flags_.find(id);
if (it == task_flags_.end()) {
// Two possibilities:
//
// 1. The task has already been untracked.
// 2. The TaskId is bad or unknown.
//
// Since this function is best-effort, it's OK to ignore these.
return;
}
it->second->data.Set();
// Remove |id| from |task_flags_| immediately, since we have no further
// use for tracking it. This allows the reply closures (see
// PostTaskAndReply()) for cancelled tasks to be skipped, since they have
// no clean-up to perform.
task_flags_.erase(it);
}
void CancelableTaskTracker::TryCancelAll() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& it : task_flags_)
it.second->data.Set();
task_flags_.clear();
}
bool CancelableTaskTracker::HasTrackedTasks() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !task_flags_.empty();
}
// static
void CancelableTaskTracker::RunIfNotCanceled(
const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task) {
if (!flag->data.IsSet()) {
std::move(task).Run();
}
}
// static
void CancelableTaskTracker::RunThenUntrackIfNotCanceled(
const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task,
OnceClosure untrack) {
RunIfNotCanceled(flag, std::move(task));
RunIfNotCanceled(flag, std::move(untrack));
}
// static
bool CancelableTaskTracker::IsCanceled(
const scoped_refptr<TaskCancellationFlag>& flag,
const ScopedClosureRunner& cleanup_runner) {
return flag->data.IsSet();
}
void CancelableTaskTracker::Track(TaskId id,
scoped_refptr<TaskCancellationFlag> flag) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(weak_this_);
bool success = task_flags_.insert(std::make_pair(id, std::move(flag))).second;
DCHECK(success);
}
void CancelableTaskTracker::Untrack(TaskId id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(weak_this_);
size_t num = task_flags_.erase(id);
DCHECK_EQ(1u, num);
}
} // namespace base

View File

@@ -0,0 +1,164 @@
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// CancelableTaskTracker posts tasks (in the form of a OnceClosure) to a
// TaskRunner, and is able to cancel the task later if it's not needed
// anymore. On destruction, CancelableTaskTracker will cancel all
// tracked tasks.
//
// Each cancelable task can be associated with a reply (also a OnceClosure).
// After the task is run on the TaskRunner, |reply| will be posted back to
// originating TaskRunner.
//
// NOTE:
//
// CancelableOnceCallback (base/cancelable_callback.h) and WeakPtr binding are
// preferred solutions for canceling a task. However, they don't support
// cancelation from another sequence. This is sometimes a performance critical
// requirement. E.g. We need to cancel database lookup task on DB thread when
// user changes inputted text. If it is performance critical to do a best effort
// cancelation of a task, then CancelableTaskTracker is appropriate, otherwise
// use one of the other mechanisms.
//
// THREAD-SAFETY:
//
// 1. A CancelableTaskTracker object must be created, used, and destroyed on a
// single sequence.
//
// 2. It's safe to destroy a CancelableTaskTracker while there are outstanding
// tasks. This is commonly used to cancel all outstanding tasks.
//
// 3. The task is deleted on the target sequence, and the reply are deleted on
// the originating sequence.
//
// 4. IsCanceledCallback can be run or deleted on any sequence.
#ifndef BASE_TASK_CANCELABLE_TASK_TRACKER_H_
#define BASE_TASK_CANCELABLE_TASK_TRACKER_H_
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/base_export.h"
#include "base/containers/small_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/post_task_and_reply_with_result_internal.h"
namespace base {
class Location;
class ScopedClosureRunner;
class TaskRunner;
class BASE_EXPORT CancelableTaskTracker {
public:
// All values except kBadTaskId are valid.
typedef int64_t TaskId;
static const TaskId kBadTaskId;
using IsCanceledCallback = RepeatingCallback<bool()>;
CancelableTaskTracker();
CancelableTaskTracker(const CancelableTaskTracker&) = delete;
CancelableTaskTracker& operator=(const CancelableTaskTracker&) = delete;
// Cancels all tracked tasks.
~CancelableTaskTracker();
TaskId PostTask(TaskRunner* task_runner,
const Location& from_here,
OnceClosure task);
TaskId PostTaskAndReply(TaskRunner* task_runner,
const Location& from_here,
OnceClosure task,
OnceClosure reply);
template <typename TaskReturnType, typename ReplyArgType>
TaskId PostTaskAndReplyWithResult(TaskRunner* task_runner,
const Location& from_here,
OnceCallback<TaskReturnType()> task,
OnceCallback<void(ReplyArgType)> reply) {
auto* result = new std::unique_ptr<TaskReturnType>();
return PostTaskAndReply(
task_runner, from_here,
BindOnce(&internal::ReturnAsParamAdapter<TaskReturnType>,
std::move(task), Unretained(result)),
BindOnce(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
std::move(reply), Owned(result)));
}
// Creates a tracked TaskId and an associated IsCanceledCallback. Client can
// later call TryCancel() with the returned TaskId, and run |is_canceled_cb|
// from any thread to check whether the TaskId is canceled.
//
// The returned task ID is tracked until the last copy of
// |is_canceled_cb| is destroyed.
//
// Note. This function is used to address some special cancelation requirement
// in existing code. You SHOULD NOT need this function in new code.
TaskId NewTrackedTaskId(IsCanceledCallback* is_canceled_cb);
// After calling this function, |task| and |reply| will not run. If the
// cancelation happens when |task| is running or has finished running, |reply|
// will not run. If |reply| is running or has finished running, cancellation
// is a noop.
//
// Note. It's OK to cancel a |task| for more than once. The later calls are
// noops.
void TryCancel(TaskId id);
// It's OK to call this function for more than once. The later calls are
// noops.
void TryCancelAll();
// Returns true iff there are in-flight tasks that are still being
// tracked.
bool HasTrackedTasks() const;
private:
// Cancellation flags are ref-counted to ensure they remain valid even if the
// tracker and its calling thread are torn down while there are still
// cancelable tasks queued to the target TaskRunner.
// See https://crbug.com/918948.
using TaskCancellationFlag = RefCountedData<AtomicFlag>;
static void RunIfNotCanceled(const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task);
static void RunThenUntrackIfNotCanceled(
const scoped_refptr<TaskCancellationFlag>& flag,
OnceClosure task,
OnceClosure untrack);
static bool IsCanceled(const scoped_refptr<TaskCancellationFlag>& flag,
const ScopedClosureRunner& cleanup_runner);
void Track(TaskId id, scoped_refptr<TaskCancellationFlag> flag);
void Untrack(TaskId id);
// Typically the number of tasks are 0-2 and occationally 3-4. But since
// this is a general API that could be used in unexpected ways, use a
// small_map instead of a flat_map to avoid falling over if there are many
// tasks.
small_map<std::map<TaskId, scoped_refptr<TaskCancellationFlag>>, 4>
task_flags_;
TaskId next_id_ = 1;
SEQUENCE_CHECKER(sequence_checker_);
// TODO(crbug.com/40050290): Remove once crasher is resolved.
base::WeakPtr<CancelableTaskTracker> weak_this_;
base::WeakPtrFactory<CancelableTaskTracker> weak_factory_{this};
};
} // namespace base
#endif // BASE_TASK_CANCELABLE_TASK_TRACKER_H_

View File

@@ -0,0 +1,7 @@
monorail: {
component: "Internals>TaskScheduling"
}
team_email: "scheduler-dev@chromium.org"
buganizer_public: {
component_id: 1456866
}

View File

@@ -0,0 +1,2 @@
file://base/task/sequence_manager/OWNERS
file://base/task/thread_pool/OWNERS

View File

@@ -0,0 +1,153 @@
// 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.
#ifndef BASE_TASK_COMMON_CHECKED_LOCK_H_
#define BASE_TASK_COMMON_CHECKED_LOCK_H_
#include <optional>
#include "base/check_op.h"
#include "base/dcheck_is_on.h"
#include "base/memory/stack_allocated.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/task/common/checked_lock_impl.h"
#include "base/thread_annotations.h"
namespace base {
namespace internal {
// CheckedLock should be used anywhere a Lock would be used in the base/task
// impl. When DCHECK_IS_ON(), lock checking occurs. Otherwise, CheckedLock is
// equivalent to base::Lock.
//
// The shape of CheckedLock is as follows:
// CheckedLock()
// Default constructor, no predecessor lock.
// DCHECKs
// On Acquisition if any CheckedLock is acquired on this thread.
// Okay if a universal predecessor is acquired.
//
// CheckedLock(const CheckedLock* predecessor)
// Constructor that specifies an allowed predecessor for that lock.
// DCHECKs
// On Construction if |predecessor| forms a predecessor lock cycle or
// is a universal successor.
// On Acquisition if the previous lock acquired on the thread is not
// either |predecessor| or a universal predecessor. Okay if there
// was no previous lock acquired.
//
// CheckedLock(UniversalPredecessor universal_predecessor)
// Constructor for a lock that will allow the acquisition of any lock after
// it, without needing to explicitly be named a predecessor (e.g. a root in
// a lock chain). Can only be acquired if no locks are currently held by
// this thread. DCHECKs
// On Acquisition if any CheckedLock is acquired on this thread.
//
// CheckedLock(UniversalSuccessor universal_successor)
// Constructor for a lock that will allow its acquisition after any other
// lock, without needing to explicitly name its predecessor (e.g. a leaf in
// a lock chain). Can not be acquired after another UniversalSuccessor lock.
// DCHECKs
// On Acquisition if there was a previously acquired lock on the thread
// and it was also a universal successor.
//
// void Acquire()
// Acquires the lock.
//
// void Release()
// Releases the lock.
//
// void AssertAcquired().
// DCHECKs if the lock is not acquired.
//
// ConditionVariable CreateConditionVariable()
// Creates a condition variable using this as a lock.
#if DCHECK_IS_ON()
class LOCKABLE CheckedLock : public CheckedLockImpl {
public:
CheckedLock() = default;
explicit CheckedLock(const CheckedLock* predecessor)
: CheckedLockImpl(predecessor) {}
explicit CheckedLock(UniversalPredecessor universal_predecessor)
: CheckedLockImpl(universal_predecessor) {}
explicit CheckedLock(UniversalSuccessor universal_successor)
: CheckedLockImpl(universal_successor) {}
};
#else // DCHECK_IS_ON()
class LOCKABLE CheckedLock : public Lock {
public:
CheckedLock() = default;
explicit CheckedLock(const CheckedLock*) {}
explicit CheckedLock(UniversalPredecessor) {}
explicit CheckedLock(UniversalSuccessor) {}
static void AssertNoLockHeldOnCurrentThread() {}
ConditionVariable CreateConditionVariable() {
return ConditionVariable(this);
}
void CreateConditionVariableAndEmplace(
std::optional<ConditionVariable>& opt) {
opt.emplace(this);
}
};
#endif // DCHECK_IS_ON()
// Provides the same functionality as base::AutoLock for CheckedLock.
using CheckedAutoLock = internal::BasicAutoLock<CheckedLock>;
// Provides the same functionality as base::AutoUnlock for CheckedLock.
using CheckedAutoUnlock = internal::BasicAutoUnlock<CheckedLock>;
// Provides the same functionality as base::AutoLockMaybe for CheckedLock.
using CheckedAutoLockMaybe = internal::BasicAutoLockMaybe<CheckedLock>;
// Informs the clang thread safety analysis that an aliased lock is acquired.
// Because the clang thread safety analysis doesn't understand aliased locks
// [1], this code wouldn't compile without AnnotateAcquiredLockAlias:
//
// class Example {
// public:
// CheckedLock lock_;
// int value = 0 GUARDED_BY(lock_);
// };
//
// Example example;
// CheckedLock* acquired = &example.lock_;
// CheckedAutoLock auto_lock(*acquired);
// AnnotateAcquiredLockAlias annotate(*acquired, example.lock_);
// example.value = 42; // Doesn't compile without |annotate|.
//
// [1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#no-alias-analysis
class SCOPED_LOCKABLE AnnotateAcquiredLockAlias {
STACK_ALLOCATED();
public:
// |acquired_lock| is an acquired lock. |lock_alias| is an alias of
// |acquired_lock|.
AnnotateAcquiredLockAlias(const CheckedLock& acquired_lock,
const CheckedLock& lock_alias)
EXCLUSIVE_LOCK_FUNCTION(lock_alias)
: acquired_lock_(acquired_lock) {
DCHECK_EQ(&acquired_lock, &lock_alias);
acquired_lock_.AssertAcquired();
}
AnnotateAcquiredLockAlias(const AnnotateAcquiredLockAlias&) = delete;
AnnotateAcquiredLockAlias& operator=(const AnnotateAcquiredLockAlias&) =
delete;
~AnnotateAcquiredLockAlias() UNLOCK_FUNCTION() {
acquired_lock_.AssertAcquired();
}
private:
const CheckedLock& acquired_lock_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_COMMON_CHECKED_LOCK_H_

View File

@@ -0,0 +1,195 @@
// 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/common/checked_lock_impl.h"
#include <optional>
#include <ostream>
#include <unordered_map>
#include <vector>
#include "base/check_op.h"
#include "base/lazy_instance.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/ranges/algorithm.h"
#include "base/synchronization/condition_variable.h"
#include "base/task/common/checked_lock.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_local.h"
namespace base {
namespace internal {
namespace {
class SafeAcquisitionTracker {
public:
SafeAcquisitionTracker() = default;
SafeAcquisitionTracker(const SafeAcquisitionTracker&) = delete;
SafeAcquisitionTracker& operator=(const SafeAcquisitionTracker&) = delete;
void RegisterLock(const CheckedLockImpl* const lock,
const CheckedLockImpl* const predecessor) {
DCHECK_NE(lock, predecessor) << "Reentrant locks are unsupported.";
AutoLock auto_lock(allowed_predecessor_map_lock_);
allowed_predecessor_map_[lock] = predecessor;
AssertSafePredecessor(lock);
}
void UnregisterLock(const CheckedLockImpl* const lock) {
AutoLock auto_lock(allowed_predecessor_map_lock_);
allowed_predecessor_map_.erase(lock);
}
void RecordAcquisition(const CheckedLockImpl* const lock) {
AssertSafeAcquire(lock);
GetAcquiredLocksOnCurrentThread()->push_back(lock);
}
void RecordRelease(const CheckedLockImpl* const lock) {
LockVector* acquired_locks = GetAcquiredLocksOnCurrentThread();
const auto iter_at_lock = ranges::find(*acquired_locks, lock);
CHECK(iter_at_lock != acquired_locks->end(), base::NotFatalUntil::M125);
acquired_locks->erase(iter_at_lock);
}
void AssertNoLockHeldOnCurrentThread() {
DCHECK(GetAcquiredLocksOnCurrentThread()->empty());
}
private:
using LockVector = std::vector<const CheckedLockImpl*>;
using PredecessorMap =
std::unordered_map<const CheckedLockImpl*, const CheckedLockImpl*>;
// This asserts that the lock is safe to acquire. This means that this should
// be run before actually recording the acquisition.
void AssertSafeAcquire(const CheckedLockImpl* const lock) {
const LockVector* acquired_locks = GetAcquiredLocksOnCurrentThread();
// If the thread currently holds no locks, this is inherently safe.
if (acquired_locks->empty())
return;
// A universal predecessor may not be acquired after any other lock.
DCHECK(!lock->is_universal_predecessor());
// Otherwise, make sure that the previous lock acquired is either an
// allowed predecessor for this lock or a universal predecessor.
const CheckedLockImpl* previous_lock = acquired_locks->back();
if (previous_lock->is_universal_predecessor())
return;
AutoLock auto_lock(allowed_predecessor_map_lock_);
// Using at() is exception-safe here as |lock| was registered already.
const CheckedLockImpl* allowed_predecessor =
allowed_predecessor_map_.at(lock);
if (lock->is_universal_successor()) {
DCHECK(!previous_lock->is_universal_successor());
return;
} else {
DCHECK_EQ(previous_lock, allowed_predecessor);
}
}
// Asserts that |lock|'s registered predecessor is safe. Because
// CheckedLocks are registered at construction time and any predecessor
// specified on a CheckedLock must already exist, the first registered
// CheckedLock in a potential chain must have a null predecessor and is thus
// cycle-free. Any subsequent CheckedLock with a predecessor must come from
// the set of registered CheckedLocks. Since the registered CheckedLocks
// only contain cycle-free CheckedLocks, this subsequent CheckedLock is
// itself cycle-free and may be safely added to the registered CheckedLock
// set.
void AssertSafePredecessor(const CheckedLockImpl* lock) const {
allowed_predecessor_map_lock_.AssertAcquired();
// Using at() is exception-safe here as |lock| was registered already.
const CheckedLockImpl* predecessor = allowed_predecessor_map_.at(lock);
if (predecessor) {
DCHECK(allowed_predecessor_map_.find(predecessor) !=
allowed_predecessor_map_.end())
<< "CheckedLock was registered before its predecessor. "
<< "Potential cycle detected";
}
}
LockVector* GetAcquiredLocksOnCurrentThread() {
if (!tls_acquired_locks_.Get())
tls_acquired_locks_.Set(std::make_unique<LockVector>());
return tls_acquired_locks_.Get();
}
// Synchronizes access to |allowed_predecessor_map_|.
Lock allowed_predecessor_map_lock_;
// A map of allowed predecessors.
PredecessorMap allowed_predecessor_map_;
// A thread-local slot holding a vector of locks currently acquired on the
// current thread.
// LockVector is not a vector<raw_ptr> due to performance regressions detected
// in blink_perf.accessibility tests.
RAW_PTR_EXCLUSION ThreadLocalOwnedPointer<LockVector> tls_acquired_locks_;
};
LazyInstance<SafeAcquisitionTracker>::Leaky g_safe_acquisition_tracker =
LAZY_INSTANCE_INITIALIZER;
} // namespace
CheckedLockImpl::CheckedLockImpl() : CheckedLockImpl(nullptr) {}
CheckedLockImpl::CheckedLockImpl(const CheckedLockImpl* predecessor) {
DCHECK(predecessor == nullptr || !predecessor->is_universal_successor_);
g_safe_acquisition_tracker.Get().RegisterLock(this, predecessor);
}
CheckedLockImpl::CheckedLockImpl(UniversalPredecessor)
: is_universal_predecessor_(true) {}
CheckedLockImpl::CheckedLockImpl(UniversalSuccessor)
: is_universal_successor_(true) {
g_safe_acquisition_tracker.Get().RegisterLock(this, nullptr);
}
CheckedLockImpl::~CheckedLockImpl() {
g_safe_acquisition_tracker.Get().UnregisterLock(this);
}
void CheckedLockImpl::AssertNoLockHeldOnCurrentThread() {
g_safe_acquisition_tracker.Get().AssertNoLockHeldOnCurrentThread();
}
void CheckedLockImpl::Acquire(subtle::LockTracking tracking) {
lock_.Acquire(tracking);
g_safe_acquisition_tracker.Get().RecordAcquisition(this);
}
void CheckedLockImpl::Release() {
lock_.Release();
g_safe_acquisition_tracker.Get().RecordRelease(this);
}
void CheckedLockImpl::AssertAcquired() const {
lock_.AssertAcquired();
}
void CheckedLockImpl::AssertNotHeld() const {
lock_.AssertNotHeld();
}
ConditionVariable CheckedLockImpl::CreateConditionVariable() {
return ConditionVariable(&lock_);
}
void CheckedLockImpl::CreateConditionVariableAndEmplace(
std::optional<ConditionVariable>& opt) {
opt.emplace(&lock_);
}
} // namespace internal
} // namespace base

View File

@@ -0,0 +1,62 @@
// 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.
#ifndef BASE_TASK_COMMON_CHECKED_LOCK_IMPL_H_
#define BASE_TASK_COMMON_CHECKED_LOCK_IMPL_H_
#include <optional>
#include "base/base_export.h"
#include "base/synchronization/lock.h"
namespace base {
class ConditionVariable;
namespace internal {
struct UniversalPredecessor {};
struct UniversalSuccessor {};
// A regular lock with simple deadlock correctness checking.
// This lock tracks all of the available locks to make sure that any locks are
// acquired in an expected order.
// See scheduler_lock.h for details.
class BASE_EXPORT CheckedLockImpl {
public:
CheckedLockImpl();
explicit CheckedLockImpl(const CheckedLockImpl* predecessor);
explicit CheckedLockImpl(UniversalPredecessor);
explicit CheckedLockImpl(UniversalSuccessor);
CheckedLockImpl(const CheckedLockImpl&) = delete;
CheckedLockImpl& operator=(const CheckedLockImpl&) = delete;
~CheckedLockImpl();
static void AssertNoLockHeldOnCurrentThread();
void Acquire(subtle::LockTracking tracking = subtle::LockTracking::kDisabled)
EXCLUSIVE_LOCK_FUNCTION(lock_);
void Release() UNLOCK_FUNCTION(lock_);
void AssertAcquired() const;
void AssertNotHeld() const;
ConditionVariable CreateConditionVariable();
void CreateConditionVariableAndEmplace(std::optional<ConditionVariable>& opt);
bool is_universal_predecessor() const { return is_universal_predecessor_; }
bool is_universal_successor() const { return is_universal_successor_; }
private:
Lock lock_;
const bool is_universal_predecessor_ = false;
const bool is_universal_successor_ = false;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_COMMON_CHECKED_LOCK_IMPL_H_

View File

@@ -0,0 +1,41 @@
// Copyright 2018 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/common/lazy_now.h"
#include <optional>
#include "base/check.h"
#include "base/time/tick_clock.h"
namespace base {
LazyNow::LazyNow(TimeTicks now) : now_(now), tick_clock_(nullptr) {}
LazyNow::LazyNow(std::optional<TimeTicks> now, const TickClock* tick_clock)
: now_(now), tick_clock_(tick_clock) {
DCHECK(tick_clock);
}
LazyNow::LazyNow(const TickClock* tick_clock) : tick_clock_(tick_clock) {
DCHECK(tick_clock);
}
LazyNow::LazyNow(LazyNow&& move_from) noexcept
: now_(move_from.now_), tick_clock_(move_from.tick_clock_) {
move_from.tick_clock_ = nullptr;
move_from.now_ = std::nullopt;
}
TimeTicks LazyNow::Now() {
// It looks tempting to avoid using Optional and to rely on is_null() instead,
// but in some test environments clock intentionally starts from zero.
if (!now_) {
DCHECK(tick_clock_); // It can fire only on use after std::move.
now_ = tick_clock_->NowTicks();
}
return *now_;
}
} // namespace base

View File

@@ -0,0 +1,45 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_COMMON_LAZY_NOW_H_
#define BASE_TASK_COMMON_LAZY_NOW_H_
#include <optional>
#include "base/base_export.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/time/time.h"
namespace base {
class TickClock;
// Now() is somewhat expensive so it makes sense not to call Now() unless we
// really need to and to avoid subsequent calls if already called once.
// LazyNow objects are expected to be short-living to represent accurate time.
class BASE_EXPORT LazyNow {
public:
explicit LazyNow(TimeTicks now);
explicit LazyNow(std::optional<TimeTicks> now, const TickClock* tick_clock);
explicit LazyNow(const TickClock* tick_clock);
LazyNow(const LazyNow&) = delete;
LazyNow& operator=(const LazyNow&) = delete;
LazyNow(LazyNow&& move_from) noexcept;
// Result will not be updated on any subsesequent calls.
TimeTicks Now();
bool has_value() const { return !!now_; }
private:
std::optional<TimeTicks> now_;
// RAW_PTR_EXCLUSION: The pointee doesn't need UaF protection (it has the same
// lifetime as the thread/sequence).
RAW_PTR_EXCLUSION const TickClock* tick_clock_; // Not owned.
};
} // namespace base
#endif // BASE_TASK_COMMON_LAZY_NOW_H_

View File

@@ -0,0 +1,108 @@
// Copyright 2018 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/common/operations_controller.h"
#include "base/check_op.h"
#include "base/synchronization/waitable_event.h"
#include <ostream>
namespace base {
namespace internal {
OperationsController::OperationsController() = default;
OperationsController::~OperationsController() {
#if DCHECK_IS_ON()
// An OperationsController may only be deleted when it was either not
// accepting operations or after it was shutdown and there are no in flight
// attempts to perform operations.
auto value = state_and_count_.load();
DCHECK(
ExtractState(value) == State::kRejectingOperations ||
(ExtractState(value) == State::kShuttingDown && ExtractCount(value) == 0))
<< value;
#endif
}
bool OperationsController::StartAcceptingOperations() {
// Release semantics are required to ensure that all memory accesses made on
// this thread happen-before any others done on a thread which is later
// allowed to perform an operation.
auto prev_value = state_and_count_.fetch_or(kAcceptingOperationsBitMask,
std::memory_order_release);
DCHECK_EQ(ExtractState(prev_value), State::kRejectingOperations);
// The count is the number of rejected operations, unwind them now.
auto num_rejected = ExtractCount(prev_value);
DecrementBy(num_rejected);
return num_rejected != 0;
}
OperationsController::OperationToken OperationsController::TryBeginOperation() {
// Acquire semantics are required to ensure that a thread which is allowed to
// perform an operation sees all the memory side-effects that happened-before
// StartAcceptingOperations(). They're also required so that no operations on
// this thread (e.g. the operation itself) can be reordered before this one.
auto prev_value = state_and_count_.fetch_add(1, std::memory_order_acquire);
switch (ExtractState(prev_value)) {
case State::kRejectingOperations:
return OperationToken(nullptr);
case State::kAcceptingOperations:
return OperationToken(this);
case State::kShuttingDown:
DecrementBy(1);
return OperationToken(nullptr);
}
}
void OperationsController::ShutdownAndWaitForZeroOperations() {
// Acquire semantics are required to guarantee that all memory side-effects
// made by other threads that were allowed to perform operations are
// synchronized with this thread before it returns from this method.
auto prev_value = state_and_count_.fetch_or(kShuttingDownBitMask,
std::memory_order_acquire);
switch (ExtractState(prev_value)) {
case State::kRejectingOperations:
// The count is the number of rejected operations, unwind them now.
DecrementBy(ExtractCount(prev_value));
break;
case State::kAcceptingOperations:
if (ExtractCount(prev_value) != 0) {
shutdown_complete_.Wait();
}
break;
case State::kShuttingDown:
DCHECK(false) << "Multiple calls to ShutdownAndWaitForZeroOperations()";
break;
}
}
// static
OperationsController::State OperationsController::ExtractState(uint32_t value) {
if (value & kShuttingDownBitMask) {
return State::kShuttingDown;
} else if (value & kAcceptingOperationsBitMask) {
return State::kAcceptingOperations;
} else {
return State::kRejectingOperations;
}
}
void OperationsController::DecrementBy(uint32_t n) {
// Release semantics are required to ensure that no operation on the current
// thread (e.g. the operation itself) can be reordered after this one.
auto prev_value = state_and_count_.fetch_sub(n, std::memory_order_release);
DCHECK_LE(n, ExtractCount(prev_value)) << "Decrement underflow";
if (ExtractState(prev_value) == State::kShuttingDown &&
ExtractCount(prev_value) == n) {
shutdown_complete_.Signal();
}
}
} // namespace internal
} // namespace base

View File

@@ -0,0 +1,156 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_
#define BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_
#include <atomic>
#include <cstdint>
#include "base/base_export.h"
#include "base/memory/stack_allocated.h"
#include "base/synchronization/waitable_event.h"
namespace base {
namespace internal {
// A lock-free thread-safe controller to manage critical multi-threaded
// operations without locks.
//
// The controller is used to determine if operations are allowed, and to keep
// track of how many are currently active. Users will call TryBeginOperation()
// before starting such operations. If the call succeeds the user can run the
// operation and the controller will keep track of it until the user signals
// that the operation is completed. No operations are allowed before
// StartAcceptingOperations() is called, or after
// ShutdownAndWaitForZeroOperations() is called.
//
// There is no explicit way of telling the controller when an operation is
// completed, instead for convenience TryBeginOperation() will return a RAII
// like object that will do so on destruction.
//
// For example:
//
// OperationsController controller_;
//
// void SetUp() {
// controller_.StartAcceptingOperations();
// }
//
// void TearDown() {
// controller_.ShutdownAndWaitForZeroOperations();
// }
//
// void MaybeRunOperation() {
// auto operation_token = controller_.TryBeginOperation();
// if (operation_token) {
// Process();
// }
// }
//
// This class is thread-safe.
// But note that StartAcceptingOperations can never be called after
// ShutdownAndWaitForZeroOperations.
class BASE_EXPORT OperationsController {
public:
// The owner of an OperationToken which evaluates to true can safely perform
// an operation while being certain it happens-after
// StartAcceptingOperations() and happens-before
// ShutdownAndWaitForZeroOperations(). Releasing this OperationToken
// relinquishes this right.
//
// This class is thread-safe
class OperationToken {
STACK_ALLOCATED();
public:
~OperationToken() {
if (outer_)
outer_->DecrementBy(1);
}
OperationToken(const OperationToken&) = delete;
OperationToken(OperationToken&& other) {
this->outer_ = other.outer_;
other.outer_ = nullptr;
}
operator bool() const { return !!outer_; }
private:
friend class OperationsController;
explicit OperationToken(OperationsController* outer) : outer_(outer) {}
OperationsController* outer_;
};
OperationsController();
// Users must call ShutdownAndWaitForZeroOperations() before destroying an
// instance of this class if StartAcceptingOperations() was called.
~OperationsController();
OperationsController(const OperationsController&) = delete;
OperationsController& operator=(const OperationsController&) = delete;
// Starts to accept operations (before this point TryBeginOperation() returns
// an invalid token). Returns true if an attempt to perform an operation was
// made and denied before StartAcceptingOperations() was called. Can be called
// at most once, never after ShutdownAndWaitForZeroOperations().
bool StartAcceptingOperations();
// Returns a RAII like object that implicitly converts to true if operations
// are allowed i.e. if this call happens-after StartAcceptingOperations() and
// happens-before Shutdown(), otherwise the object will convert to false. On
// successful return, this OperationsController will keep track of the
// operation until the returned object goes out of scope.
OperationToken TryBeginOperation();
// Prevents further calls to TryBeginOperation() from succeeding and waits for
// all the ongoing operations to complete.
//
// Attention: Can only be called once.
void ShutdownAndWaitForZeroOperations();
private:
// Atomic representation of the state of this class. We use the upper 2 bits
// to keep track of flag like values and the remainder bits are used as a
// counter. The 2 flags are used to represent 3 different states:
//
// State | AcceptOperations Bit | ShuttingDown Bit
// --------------------------------------------------------------
// kRejectingOperations | 0 | 0
// kAcceptingOperations | 1 | 0
// kShuttingDown | * | 1
//
// The counter keeps track of the rejected operations when we are in
// the kRejectingOperations state, the number of inflight operations
// otherwise. If the count reaches zero and we are in the shutting down state
// |shutdown_complete_| will be signaled.
static constexpr uint32_t kShuttingDownBitMask = uint32_t{1} << 31;
static constexpr uint32_t kAcceptingOperationsBitMask = uint32_t{1} << 30;
static constexpr uint32_t kFlagsBitMask =
(kShuttingDownBitMask | kAcceptingOperationsBitMask);
static constexpr uint32_t kCountBitMask = ~kFlagsBitMask;
enum class State {
kRejectingOperations,
kAcceptingOperations,
kShuttingDown,
};
// Helper methods for the bit fiddling. Pass a |state_and_count_| value to
// extract state or count out of it.
static uint32_t ExtractCount(uint32_t value) { return value & kCountBitMask; }
static State ExtractState(uint32_t value);
// Decrements the counter by |n| and signals |shutdown_complete_| if needed.
void DecrementBy(uint32_t n);
std::atomic<uint32_t> state_and_count_{0};
WaitableEvent shutdown_complete_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_

View File

@@ -0,0 +1,101 @@
// Copyright 2019 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/common/scoped_defer_task_posting.h"
#include "base/compiler_specific.h"
namespace base {
namespace {
// Holds a thread-local pointer to the current scope or null when no
// scope is active.
constinit thread_local ScopedDeferTaskPosting* scoped_defer_task_posting =
nullptr;
} // namespace
// static
void ScopedDeferTaskPosting::PostOrDefer(
scoped_refptr<SequencedTaskRunner> task_runner,
const Location& from_here,
OnceClosure task,
base::TimeDelta delay) {
ScopedDeferTaskPosting* scope = Get();
if (scope) {
scope->DeferTaskPosting(std::move(task_runner), from_here, std::move(task),
delay);
return;
}
task_runner->PostDelayedTask(from_here, std::move(task), delay);
}
// static
ScopedDeferTaskPosting* ScopedDeferTaskPosting::Get() {
// Workaround false-positive MSAN use-of-uninitialized-value on
// thread_local storage for loaded libraries:
// https://github.com/google/sanitizers/issues/1265
MSAN_UNPOISON(&scoped_defer_task_posting, sizeof(ScopedDeferTaskPosting*));
return scoped_defer_task_posting;
}
// static
bool ScopedDeferTaskPosting::Set(ScopedDeferTaskPosting* scope) {
// We can post a task from within a ScheduleWork in some tests, so we can
// get nested scopes. In this case ignore all except the top one.
if (Get() && scope)
return false;
scoped_defer_task_posting = scope;
return true;
}
// static
bool ScopedDeferTaskPosting::IsPresent() {
return !!Get();
}
ScopedDeferTaskPosting::ScopedDeferTaskPosting() {
top_level_scope_ = Set(this);
}
ScopedDeferTaskPosting::~ScopedDeferTaskPosting() {
if (!top_level_scope_) {
DCHECK(deferred_tasks_.empty());
return;
}
Set(nullptr);
for (DeferredTask& deferred_task : deferred_tasks_) {
deferred_task.task_runner->PostDelayedTask(deferred_task.from_here,
std::move(deferred_task.task),
deferred_task.delay);
}
}
ScopedDeferTaskPosting::DeferredTask::DeferredTask(
scoped_refptr<SequencedTaskRunner> task_runner,
Location from_here,
OnceClosure task,
base::TimeDelta delay)
: task_runner(std::move(task_runner)),
from_here(from_here),
task(std::move(task)),
delay(delay) {}
ScopedDeferTaskPosting::DeferredTask::DeferredTask(DeferredTask&&) = default;
ScopedDeferTaskPosting::DeferredTask::~DeferredTask() = default;
void ScopedDeferTaskPosting::DeferTaskPosting(
scoped_refptr<SequencedTaskRunner> task_runner,
const Location& from_here,
OnceClosure task,
base::TimeDelta delay) {
deferred_tasks_.push_back(
{std::move(task_runner), from_here, std::move(task), delay});
}
} // namespace base

View File

@@ -0,0 +1,82 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_COMMON_SCOPED_DEFER_TASK_POSTING_H_
#define BASE_TASK_COMMON_SCOPED_DEFER_TASK_POSTING_H_
#include <vector>
#include "base/base_export.h"
#include "base/location.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
namespace base {
// Tracing wants to post tasks from within a trace event within PostTask, but
// this can lead to a deadlock. Create a scope to ensure that we are posting
// the tasks in question outside of the scope of the lock.
// NOTE: This scope affects only the thread it is created on. All other threads
// still can post tasks.
//
// TODO(altimin): It should be possible to get rid of this scope, but this
// requires refactoring TimeDomain to ensure that TimeDomain never changes and
// we can read current time without grabbing a lock.
class BASE_EXPORT [[maybe_unused, nodiscard]] ScopedDeferTaskPosting {
public:
static void PostOrDefer(scoped_refptr<SequencedTaskRunner> task_runner,
const Location& from_here,
OnceClosure task,
base::TimeDelta delay);
static bool IsPresent();
ScopedDeferTaskPosting();
ScopedDeferTaskPosting(const ScopedDeferTaskPosting&) = delete;
ScopedDeferTaskPosting& operator=(const ScopedDeferTaskPosting&) = delete;
~ScopedDeferTaskPosting();
private:
static ScopedDeferTaskPosting* Get();
// Returns whether the |scope| was set as active, which happens only
// when the scope wasn't set before.
static bool Set(ScopedDeferTaskPosting* scope);
void DeferTaskPosting(scoped_refptr<SequencedTaskRunner> task_runner,
const Location& from_here,
OnceClosure task,
base::TimeDelta delay);
struct DeferredTask {
DeferredTask(scoped_refptr<SequencedTaskRunner> task_runner,
Location from_here,
OnceClosure task,
base::TimeDelta delay);
DeferredTask(const DeferredTask&) = delete;
DeferredTask& operator=(const DeferredTask&) = delete;
DeferredTask(DeferredTask&& task);
~DeferredTask();
scoped_refptr<SequencedTaskRunner> task_runner;
Location from_here;
OnceClosure task;
base::TimeDelta delay;
};
std::vector<DeferredTask> deferred_tasks_;
// Scopes can be nested (e.g. ScheduleWork inside PostTasks can post a task
// to another task runner), so we want to know whether the scope is top-level
// or not.
bool top_level_scope_ = false;
};
} // namespace base
#endif // BASE_TASK_COMMON_SCOPED_DEFER_TASK_POSTING_H_

View File

@@ -0,0 +1,433 @@
// Copyright 2014 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/common/task_annotator.h"
#include <stdint.h>
#include <algorithm>
#include <array>
#include <string_view>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/debug/alias.h"
#include "base/hash/md5.h"
#include "base/logging.h"
#include "base/metrics/metrics_hashes.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
#include "base/tracing_buildflags.h"
#if BUILDFLAG(ENABLE_BASE_TRACING)
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_mojo_event_info.pbzero.h" // nogncheck
#endif
namespace base {
namespace {
TaskAnnotator::ObserverForTesting* g_task_annotator_observer = nullptr;
// The PendingTask currently in progress on each thread. Used to allow creating
// a breadcrumb of program counters on the stack to help identify a task's
// origin in crashes.
constinit thread_local PendingTask* current_pending_task = nullptr;
// Scoped IPC-related data (IPC hash and/or IPC interface name). IPC hash or
// interface name can be known before the associated task object is created;
// thread-local so that this data can be affixed to the associated task.
constinit thread_local TaskAnnotator::ScopedSetIpcHash*
current_scoped_ipc_hash = nullptr;
constinit thread_local TaskAnnotator::LongTaskTracker*
current_long_task_tracker = nullptr;
// These functions can be removed, and the calls below replaced with direct
// variable accesses, once the MSAN workaround is not necessary.
TaskAnnotator::ScopedSetIpcHash* GetCurrentScopedIpcHash() {
// Workaround false-positive MSAN use-of-uninitialized-value on
// thread_local storage for loaded libraries:
// https://github.com/google/sanitizers/issues/1265
MSAN_UNPOISON(&current_scoped_ipc_hash,
sizeof(TaskAnnotator::ScopedSetIpcHash*));
return current_scoped_ipc_hash;
}
TaskAnnotator::LongTaskTracker* GetCurrentLongTaskTracker() {
// Workaround false-positive MSAN use-of-uninitialized-value on
// thread_local storage for loaded libraries:
// https://github.com/google/sanitizers/issues/1265
MSAN_UNPOISON(&current_long_task_tracker,
sizeof(TaskAnnotator::LongTaskTracker*));
return current_long_task_tracker;
}
#if BUILDFLAG(ENABLE_BASE_TRACING)
perfetto::protos::pbzero::ChromeTaskAnnotator::DelayPolicy ToProtoEnum(
subtle::DelayPolicy type) {
using ProtoType = perfetto::protos::pbzero::ChromeTaskAnnotator::DelayPolicy;
switch (type) {
case subtle::DelayPolicy::kFlexibleNoSooner:
return ProtoType::FLEXIBLE_NO_SOONER;
case subtle::DelayPolicy::kFlexiblePreferEarly:
return ProtoType::FLEXIBLE_PREFER_EARLY;
case subtle::DelayPolicy::kPrecise:
return ProtoType::PRECISE;
}
}
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
} // namespace
const PendingTask* TaskAnnotator::CurrentTaskForThread() {
// Workaround false-positive MSAN use-of-uninitialized-value on
// thread_local storage for loaded libraries:
// https://github.com/google/sanitizers/issues/1265
MSAN_UNPOISON(&current_pending_task, sizeof(PendingTask*));
return current_pending_task;
}
void TaskAnnotator::OnIPCReceived(const char* interface_name,
uint32_t (*method_info)(),
bool is_response) {
auto* const tracker = GetCurrentLongTaskTracker();
if (!tracker) {
return;
}
tracker->SetIpcDetails(interface_name, method_info, is_response);
}
void TaskAnnotator::MarkCurrentTaskAsInterestingForTracing() {
auto* const tracker = GetCurrentLongTaskTracker();
if (!tracker) {
return;
}
tracker->is_interesting_task = true;
}
TaskAnnotator::TaskAnnotator() = default;
TaskAnnotator::~TaskAnnotator() = default;
void TaskAnnotator::WillQueueTask(perfetto::StaticString trace_event_name,
TaskMetadata* pending_task) {
DCHECK(pending_task);
TRACE_EVENT_INSTANT(
"toplevel.flow", trace_event_name,
perfetto::Flow::ProcessScoped(GetTaskTraceID(*pending_task)));
DCHECK(!pending_task->task_backtrace[0])
<< "Task backtrace was already set, task posted twice??";
if (pending_task->task_backtrace[0])
return;
DCHECK(!pending_task->ipc_interface_name);
DCHECK(!pending_task->ipc_hash);
const auto* const hash = GetCurrentScopedIpcHash();
if (hash) {
pending_task->ipc_interface_name = hash->GetIpcInterfaceName();
pending_task->ipc_hash = hash->GetIpcHash();
}
const auto* parent_task = CurrentTaskForThread();
if (!parent_task)
return;
pending_task->task_backtrace[0] = parent_task->posted_from.program_counter();
std::copy(parent_task->task_backtrace.begin(),
parent_task->task_backtrace.end() - 1,
pending_task->task_backtrace.begin() + 1);
pending_task->task_backtrace_overflow =
parent_task->task_backtrace_overflow ||
parent_task->task_backtrace.back() != nullptr;
}
void TaskAnnotator::RunTaskImpl(PendingTask& pending_task) {
TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION(
pending_task.posted_from.file_name());
// Before running the task, store the IPC context and the task backtrace with
// the chain of PostTasks that resulted in this call and deliberately alias it
// to ensure it is on the stack if the task crashes. Be careful not to assume
// that the variable itself will have the expected value when displayed by the
// optimizer in an optimized build. Look at a memory dump of the stack.
static constexpr int kStackTaskTraceSnapshotSize =
PendingTask::kTaskBacktraceLength + 4;
std::array<const void*, kStackTaskTraceSnapshotSize> task_backtrace;
// Store a marker to locate |task_backtrace| content easily on a memory
// dump. The layout is as follows:
//
// +------------ +----+---------+-----+-----------+----------+-------------+
// | Head Marker | PC | frame 0 | ... | frame N-1 | IPC hash | Tail Marker |
// +------------ +----+---------+-----+-----------+----------+-------------+
//
// Markers glossary (compliments of wez):
// cool code,do it dude!
// 0x c001 c0de d0 17 d00d
// o dude,i did it biig
// 0x 0 d00d 1 d1d 17 8119
task_backtrace.front() = reinterpret_cast<void*>(0xc001c0ded017d00d);
task_backtrace.back() = reinterpret_cast<void*>(0x0d00d1d1d178119);
task_backtrace[1] = pending_task.posted_from.program_counter();
ranges::copy(pending_task.task_backtrace, task_backtrace.begin() + 2);
task_backtrace[kStackTaskTraceSnapshotSize - 2] =
reinterpret_cast<void*>(pending_task.ipc_hash);
debug::Alias(&task_backtrace);
// Record the task time in convenient units. This can be compared to times
// stored in places like ReportThreadHang() and BrowserMain() when analyzing
// hangs.
const int64_t task_time =
pending_task.GetDesiredExecutionTime().since_origin().InSeconds();
base::debug::Alias(&task_time);
{
const AutoReset<PendingTask*> resetter(&current_pending_task,
&pending_task);
if (g_task_annotator_observer) {
g_task_annotator_observer->BeforeRunTask(&pending_task);
}
std::move(pending_task.task).Run();
}
// Stomp the markers. Otherwise they can stick around on the unused parts of
// stack and cause |task_backtrace| to be associated with an unrelated stack
// sample on this thread later in the event of a crash. Alias once again after
// these writes to make sure the compiler doesn't optimize them out (unused
// writes to a local variable).
task_backtrace.front() = nullptr;
task_backtrace.back() = nullptr;
debug::Alias(&task_backtrace);
}
uint64_t TaskAnnotator::GetTaskTraceID(const TaskMetadata& task) const {
return (static_cast<uint64_t>(task.sequence_num) << 32) |
((static_cast<uint64_t>(reinterpret_cast<intptr_t>(this)) << 32) >>
32);
}
// static
void TaskAnnotator::RegisterObserverForTesting(ObserverForTesting* observer) {
DCHECK(!g_task_annotator_observer);
g_task_annotator_observer = observer;
}
// static
void TaskAnnotator::ClearObserverForTesting() {
g_task_annotator_observer = nullptr;
}
#if BUILDFLAG(ENABLE_BASE_TRACING)
// TRACE_EVENT argument helper, writing the task location data into
// EventContext.
void TaskAnnotator::EmitTaskLocation(perfetto::EventContext& ctx,
const PendingTask& task) {
ctx.event()->set_task_execution()->set_posted_from_iid(
base::trace_event::InternedSourceLocation::Get(&ctx, task.posted_from));
}
// TRACE_EVENT argument helper, writing the incoming task flow information
// into EventContext if toplevel.flow category is enabled.
void TaskAnnotator::MaybeEmitIncomingTaskFlow(perfetto::EventContext& ctx,
const PendingTask& task) const {
static const uint8_t* flow_enabled =
TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("toplevel.flow");
if (!*flow_enabled)
return;
perfetto::Flow::ProcessScoped(GetTaskTraceID(task))(ctx);
}
// static
void TaskAnnotator::EmitTaskTimingDetails(perfetto::EventContext& ctx) {
auto* const tracker = GetCurrentLongTaskTracker();
if (tracker) {
base::TimeTicks event_start_time = base::TimeTicks::Now();
base::TimeTicks task_start_time = tracker->GetTaskStartTime();
perfetto::protos::pbzero::CurrentTask* current_task =
ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>()
->set_current_task();
current_task->set_event_offset_from_task_start_time_us(
static_cast<uint64_t>(
(event_start_time - task_start_time).InMicroseconds()));
current_task->set_task_start_time_us(
static_cast<uint64_t>(task_start_time.since_origin().InMicroseconds()));
}
}
// static
void TaskAnnotator::MaybeEmitDelayAndPolicy(perfetto::EventContext& ctx,
const PendingTask& task) {
if (task.delayed_run_time.is_null()) {
return;
}
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* annotator = event->set_chrome_task_annotator();
annotator->set_task_delay_us(static_cast<uint64_t>(
(task.delayed_run_time - task.queue_time).InMicroseconds()));
annotator->set_delay_policy(ToProtoEnum(task.delay_policy));
}
void TaskAnnotator::MaybeEmitIPCHash(perfetto::EventContext& ctx,
const PendingTask& task) const {
static const uint8_t* toplevel_ipc_enabled =
TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
TRACE_DISABLED_BY_DEFAULT("toplevel.ipc"));
if (!*toplevel_ipc_enabled)
return;
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* annotator = event->set_chrome_task_annotator();
annotator->set_ipc_hash(task.ipc_hash);
}
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
TaskAnnotator::ScopedSetIpcHash::ScopedSetIpcHash(uint32_t ipc_hash)
: ScopedSetIpcHash(ipc_hash, nullptr) {}
TaskAnnotator::ScopedSetIpcHash::ScopedSetIpcHash(
const char* ipc_interface_name)
: ScopedSetIpcHash(0, ipc_interface_name) {}
TaskAnnotator::ScopedSetIpcHash::ScopedSetIpcHash(
uint32_t ipc_hash,
const char* ipc_interface_name)
: resetter_(&current_scoped_ipc_hash, this),
ipc_hash_(ipc_hash),
ipc_interface_name_(ipc_interface_name) {}
// Static
uint32_t TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName(
std::string_view name) {
return HashMetricNameAs32Bits(name);
}
TaskAnnotator::ScopedSetIpcHash::~ScopedSetIpcHash() {
DCHECK_EQ(this, GetCurrentScopedIpcHash());
}
TaskAnnotator::LongTaskTracker::LongTaskTracker(const TickClock* tick_clock,
PendingTask& pending_task,
TaskAnnotator* task_annotator,
TimeTicks task_start_time)
: resetter_(&current_long_task_tracker, this),
tick_clock_(tick_clock),
task_start_time_(task_start_time),
pending_task_(pending_task),
task_annotator_(task_annotator) {}
TaskAnnotator::LongTaskTracker::~LongTaskTracker() {
DCHECK_EQ(this, GetCurrentLongTaskTracker());
// Use this to ensure that NowTicks() are not called
// unnecessarily.
bool is_tracing = false;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("scheduler.long_tasks", &is_tracing);
if (!is_tracing) {
return;
}
task_end_time_ = tick_clock_->NowTicks();
MaybeTraceInterestingTaskDetails();
if ((task_end_time_ - task_start_time_) >= kMaxTaskDurationTimeDelta) {
TRACE_EVENT_BEGIN("scheduler.long_tasks", "LongTaskTracker",
perfetto::Track::ThreadScoped(task_annotator_),
task_start_time_, [&](perfetto::EventContext& ctx) {
TaskAnnotator::EmitTaskLocation(ctx, pending_task_);
EmitReceivedIPCDetails(ctx);
});
TRACE_EVENT_END("scheduler.long_tasks",
perfetto::Track::ThreadScoped(task_annotator_),
task_end_time_);
}
}
void TaskAnnotator::LongTaskTracker::SetIpcDetails(const char* interface_name,
uint32_t (*method_info)(),
bool is_response) {
ipc_interface_name_ = interface_name;
is_response_ = is_response;
if (!method_info)
return;
ipc_hash_ = (*method_info)();
ipc_method_info_ = method_info;
}
void TaskAnnotator::LongTaskTracker::EmitReceivedIPCDetails(
perfetto::EventContext& ctx) {
if (!ipc_interface_name_ || !ipc_hash_ || !ipc_method_info_)
return;
#if BUILDFLAG(ENABLE_BASE_TRACING) && !BUILDFLAG(IS_NACL)
// Emit all of the IPC hash information if this task
// comes from a mojo interface.
auto* info = ctx.event()->set_chrome_mojo_event_info();
info->set_mojo_interface_tag(ipc_interface_name_);
info->set_ipc_hash(ipc_hash_);
info->set_is_reply(is_response_);
// The Native client will not build as the relevant implementation of
// base::ModuleCache::CreateModuleForAddress is not implemented for it.
// Thus the below code must be included on a conditional basis.
const auto ipc_method_address = reinterpret_cast<uintptr_t>(ipc_method_info_);
const std::optional<size_t> location_iid =
base::trace_event::InternedUnsymbolizedSourceLocation::Get(
&ctx, ipc_method_address);
if (location_iid) {
info->set_mojo_interface_method_iid(*location_iid);
}
#endif
}
// This method is used to record the queueing time and task start time for tasks
// that may be of interest during a trace, even if they are not considered long
// tasks. For example, input - the queue time and flow information is required
// to calculate chrome input to browser intervals in perfetto, and further
// calculate the chrome tasks blocking input. We need LatencyInfo slices to be
// associated with the correct input IPCs, hence record in the LongTaskTracker.
void TaskAnnotator::LongTaskTracker::MaybeTraceInterestingTaskDetails() {
if (is_interesting_task && ipc_interface_name_) {
// Record the equivalent of a delayed instant trace event, acting as the
// start of the flow between task queue time and task execution start time.
TRACE_EVENT_INSTANT("scheduler.long_tasks", "InterestingTask_QueueingTime",
perfetto::Track::ThreadScoped(task_annotator_),
pending_task_.queue_time,
perfetto::Flow::ProcessScoped(
task_annotator_->GetTaskTraceID(pending_task_)));
// Record the equivalent of a top-level event with enough IPC information
// to calculate the input to browser interval. This event will be the
// termination of the event above, aka the start of task execution.
TRACE_EVENT_BEGIN(
"scheduler.long_tasks", "InterestingTask_ProcessingTime",
perfetto::Track::ThreadScoped(task_annotator_), task_start_time_,
[&](perfetto::EventContext& ctx) {
perfetto::TerminatingFlow::ProcessScoped(
task_annotator_->GetTaskTraceID(pending_task_))(ctx);
auto* info = ctx.event()->set_chrome_mojo_event_info();
info->set_mojo_interface_tag(ipc_interface_name_);
});
TRACE_EVENT_END("scheduler.long_tasks",
perfetto::Track::ThreadScoped(task_annotator_),
task_end_time_);
}
}
} // namespace base

View File

@@ -0,0 +1,214 @@
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_COMMON_TASK_ANNOTATOR_H_
#define BASE_TASK_COMMON_TASK_ANNOTATOR_H_
#include <stdint.h>
#include <string_view>
#include "base/auto_reset.h"
#include "base/base_export.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/pending_task.h"
#include "base/time/tick_clock.h"
#include "base/trace_event/base_tracing.h"
namespace base {
// Constant used to measure which long-running tasks should be traced.
constexpr TimeDelta kMaxTaskDurationTimeDelta = Milliseconds(4);
// Implements common debug annotations for posted tasks. This includes data
// such as task origins, IPC message contexts, queueing durations and memory
// usage.
class BASE_EXPORT TaskAnnotator {
public:
class ObserverForTesting {
public:
// Invoked just before RunTask() in the scope in which the task is about to
// be executed.
virtual void BeforeRunTask(const PendingTask* pending_task) = 0;
};
// This is used to set the |ipc_hash| field for PendingTasks. It is intended
// to be used only from within generated IPC handler dispatch code.
class ScopedSetIpcHash;
// This is used to track long-running browser-UI tasks. It is intended to
// be used for low-overhead logging to produce longer traces, particularly to
// help the scroll jank reduction effort.
class LongTaskTracker;
static const PendingTask* CurrentTaskForThread();
static void OnIPCReceived(const char* interface_name,
uint32_t (*method_info)(),
bool is_response);
static void MarkCurrentTaskAsInterestingForTracing();
#if BUILDFLAG(ENABLE_BASE_TRACING)
// TRACE_EVENT argument helper, writing the task start time into
// EventContext.
// NOTE: Should only be used with TRACE_EVENT or TRACE_EVENT_BEGIN since the
// function records the timestamp for event start at call time.
static void EmitTaskTimingDetails(perfetto::EventContext& ctx);
#endif
TaskAnnotator();
TaskAnnotator(const TaskAnnotator&) = delete;
TaskAnnotator& operator=(const TaskAnnotator&) = delete;
~TaskAnnotator();
// Called to indicate that a task is about to be queued to run in the future,
// giving one last chance for this TaskAnnotator to add metadata to
// |pending_task| before it is moved into the queue.
void WillQueueTask(perfetto::StaticString trace_event_name,
TaskMetadata* pending_task);
// Creates a process-wide unique ID to represent this task in trace events.
// This will be mangled with a Process ID hash to reduce the likelyhood of
// colliding with TaskAnnotator pointers on other processes. Callers may use
// this when generating their own flow events (i.e. when passing
// |queue_function == nullptr| in above methods).
uint64_t GetTaskTraceID(const TaskMetadata& task) const;
// Run the given task, emitting the toplevel trace event and additional
// trace event arguments. Like for TRACE_EVENT macros, all of the arguments
// are used (i.e. lambdas are invoked) before this function exits, so it's
// safe to pass reference-capturing lambdas here.
template <typename... Args>
void RunTask(perfetto::StaticString event_name,
PendingTask& pending_task,
Args&&... args) {
TRACE_EVENT(
"toplevel", event_name,
[&](perfetto::EventContext& ctx) {
EmitTaskLocation(ctx, pending_task);
MaybeEmitDelayAndPolicy(ctx, pending_task);
MaybeEmitIncomingTaskFlow(ctx, pending_task);
MaybeEmitIPCHash(ctx, pending_task);
},
std::forward<Args>(args)...);
RunTaskImpl(pending_task);
}
private:
friend class TaskAnnotatorBacktraceIntegrationTest;
// Run a previously queued task.
NOT_TAIL_CALLED void RunTaskImpl(PendingTask& pending_task);
// Registers an ObserverForTesting that will be invoked by all TaskAnnotators'
// RunTask(). This registration and the implementation of BeforeRunTask() are
// responsible to ensure thread-safety.
static void RegisterObserverForTesting(ObserverForTesting* observer);
static void ClearObserverForTesting();
#if BUILDFLAG(ENABLE_BASE_TRACING)
// TRACE_EVENT argument helper, writing the task location data into
// EventContext.
static void EmitTaskLocation(perfetto::EventContext& ctx,
const PendingTask& task);
static void MaybeEmitDelayAndPolicy(perfetto::EventContext& ctx,
const PendingTask& task);
// TRACE_EVENT argument helper, writing the incoming task flow information
// into EventContext if toplevel.flow category is enabled.
void MaybeEmitIncomingTaskFlow(perfetto::EventContext& ctx,
const PendingTask& task) const;
void MaybeEmitIPCHash(perfetto::EventContext& ctx,
const PendingTask& task) const;
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
};
class BASE_EXPORT [[maybe_unused, nodiscard]] TaskAnnotator::ScopedSetIpcHash {
public:
explicit ScopedSetIpcHash(uint32_t ipc_hash);
// Compile-time-const string identifying the current IPC context. Not always
// available due to binary size constraints, so IPC hash might be set instead.
explicit ScopedSetIpcHash(const char* ipc_interface_name);
ScopedSetIpcHash(const ScopedSetIpcHash&) = delete;
ScopedSetIpcHash& operator=(const ScopedSetIpcHash&) = delete;
~ScopedSetIpcHash();
uint32_t GetIpcHash() const { return ipc_hash_; }
const char* GetIpcInterfaceName() const { return ipc_interface_name_; }
static uint32_t MD5HashMetricName(std::string_view name);
private:
ScopedSetIpcHash(uint32_t ipc_hash, const char* ipc_interface_name);
const AutoReset<ScopedSetIpcHash*> resetter_;
uint32_t ipc_hash_;
const char* ipc_interface_name_;
};
class BASE_EXPORT [[maybe_unused, nodiscard]] TaskAnnotator::LongTaskTracker {
public:
explicit LongTaskTracker(const TickClock* tick_clock,
PendingTask& pending_task,
TaskAnnotator* task_annotator,
TimeTicks task_start_time);
LongTaskTracker(const LongTaskTracker&) = delete;
~LongTaskTracker();
void SetIpcDetails(const char* interface_name,
uint32_t (*method_info)(),
bool is_response);
void MaybeTraceInterestingTaskDetails();
// In long-task tracking, not every task (including its queue time) will be
// recorded in a trace. If a particular task + queue time needs to be
// recorded, flag it explicitly. For example, input tasks are required for
// calculating scroll jank metrics.
bool is_interesting_task = false;
TimeTicks GetTaskStartTime() const { return task_start_time_; }
private:
void EmitReceivedIPCDetails(perfetto::EventContext& ctx);
const AutoReset<LongTaskTracker*> resetter_;
// For tracking task duration.
//
// RAW_PTR_EXCLUSION: Performance reasons: based on analysis of sampling
// profiler data (TaskAnnotator::LongTaskTracker::~LongTaskTracker).
RAW_PTR_EXCLUSION const TickClock* tick_clock_; // Not owned.
// Task start time, sampled before the LongTaskTracker instance
// is created.
TimeTicks task_start_time_;
TimeTicks task_end_time_;
// Tracing variables.
const char* ipc_interface_name_ = nullptr;
uint32_t ipc_hash_ = 0;
// IPC method info to retrieve IPC hash and method address from trace, if
// known. Note that this will not compile in the Native client.
uint32_t (*ipc_method_info_)();
bool is_response_ = false;
// RAW_PTR_EXCLUSION: Performance reasons: based on analysis of sampling
// profiler data (TaskAnnotator::LongTaskTracker::~LongTaskTracker).
[[maybe_unused]] RAW_PTR_EXCLUSION PendingTask& pending_task_;
[[maybe_unused]] RAW_PTR_EXCLUSION TaskAnnotator* task_annotator_;
};
} // namespace base
#endif // BASE_TASK_COMMON_TASK_ANNOTATOR_H_

View File

@@ -0,0 +1,270 @@
// Copyright 2018 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/current_thread.h"
#include <utility>
#include "base/callback_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/message_loop/message_pump_for_io.h"
#include "base/message_loop/message_pump_for_ui.h"
#include "base/message_loop/message_pump_type.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/threading/thread_local.h"
#include "base/trace_event/base_tracing.h"
#include "build/build_config.h"
namespace base {
//------------------------------------------------------------------------------
// CurrentThread
// static
sequence_manager::internal::SequenceManagerImpl*
CurrentThread::GetCurrentSequenceManagerImpl() {
return sequence_manager::internal::SequenceManagerImpl::GetCurrent();
}
// static
CurrentThread CurrentThread::Get() {
return CurrentThread(GetCurrentSequenceManagerImpl());
}
// static
CurrentThread CurrentThread::GetNull() {
return CurrentThread(nullptr);
}
// static
bool CurrentThread::IsSet() {
return !!GetCurrentSequenceManagerImpl();
}
void CurrentThread::AddDestructionObserver(
DestructionObserver* destruction_observer) {
DCHECK(current_->IsBoundToCurrentThread());
current_->AddDestructionObserver(destruction_observer);
}
void CurrentThread::RemoveDestructionObserver(
DestructionObserver* destruction_observer) {
DCHECK(current_->IsBoundToCurrentThread());
current_->RemoveDestructionObserver(destruction_observer);
}
void CurrentThread::SetTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) {
DCHECK(current_->IsBoundToCurrentThread());
current_->SetTaskRunner(std::move(task_runner));
}
bool CurrentThread::IsBoundToCurrentThread() const {
return current_ == GetCurrentSequenceManagerImpl();
}
bool CurrentThread::IsIdleForTesting() {
DCHECK(current_->IsBoundToCurrentThread());
return current_->IsIdleForTesting();
}
void CurrentThread::EnableMessagePumpTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing) {
return current_->EnableMessagePumpTimeKeeperMetrics(
thread_name, wall_time_based_metrics_enabled_for_testing);
}
void CurrentThread::AddTaskObserver(TaskObserver* task_observer) {
DCHECK(current_->IsBoundToCurrentThread());
current_->AddTaskObserver(task_observer);
}
void CurrentThread::RemoveTaskObserver(TaskObserver* task_observer) {
DCHECK(current_->IsBoundToCurrentThread());
current_->RemoveTaskObserver(task_observer);
}
void CurrentThread::SetAddQueueTimeToTasks(bool enable) {
DCHECK(current_->IsBoundToCurrentThread());
current_->SetAddQueueTimeToTasks(enable);
}
CallbackListSubscription CurrentThread::RegisterOnNextIdleCallback(
RegisterOnNextIdleCallbackPasskey,
OnceClosure on_next_idle_callback) {
return current_->RegisterOnNextIdleCallback(std::move(on_next_idle_callback));
}
CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop::
ScopedAllowApplicationTasksInNativeNestedLoop()
: sequence_manager_(GetCurrentSequenceManagerImpl()),
previous_state_(
sequence_manager_->IsTaskExecutionAllowedInNativeNestedLoop()) {
TRACE_EVENT_BEGIN0("base", "ScopedNestableTaskAllower");
sequence_manager_->SetTaskExecutionAllowedInNativeNestedLoop(true);
}
CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop::
~ScopedAllowApplicationTasksInNativeNestedLoop() {
sequence_manager_->SetTaskExecutionAllowedInNativeNestedLoop(previous_state_);
TRACE_EVENT_END0("base", "ScopedNestableTaskAllower");
}
bool CurrentThread::ApplicationTasksAllowedInNativeNestedLoop() const {
return current_->IsTaskExecutionAllowedInNativeNestedLoop();
}
#if !BUILDFLAG(IS_NACL)
//------------------------------------------------------------------------------
// CurrentUIThread
// static
CurrentUIThread CurrentUIThread::Get() {
auto* sequence_manager = GetCurrentSequenceManagerImpl();
DCHECK(sequence_manager);
#if BUILDFLAG(IS_ANDROID)
DCHECK(sequence_manager->IsType(MessagePumpType::UI) ||
sequence_manager->IsType(MessagePumpType::JAVA));
#else // BUILDFLAG(IS_ANDROID)
DCHECK(sequence_manager->IsType(MessagePumpType::UI));
#endif // BUILDFLAG(IS_ANDROID)
return CurrentUIThread(sequence_manager);
}
// static
bool CurrentUIThread::IsSet() {
sequence_manager::internal::SequenceManagerImpl* sequence_manager =
GetCurrentSequenceManagerImpl();
return sequence_manager &&
#if BUILDFLAG(IS_ANDROID)
(sequence_manager->IsType(MessagePumpType::UI) ||
sequence_manager->IsType(MessagePumpType::JAVA));
#else // BUILDFLAG(IS_ANDROID)
sequence_manager->IsType(MessagePumpType::UI);
#endif // BUILDFLAG(IS_ANDROID)
}
MessagePumpForUI* CurrentUIThread::GetMessagePumpForUI() const {
return static_cast<MessagePumpForUI*>(current_->GetMessagePump());
}
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_WIN)
bool CurrentUIThread::WatchFileDescriptor(
int fd,
bool persistent,
MessagePumpForUI::Mode mode,
MessagePumpForUI::FdWatchController* controller,
MessagePumpForUI::FdWatcher* delegate) {
DCHECK(current_->IsBoundToCurrentThread());
return GetMessagePumpForUI()->WatchFileDescriptor(fd, persistent, mode,
controller, delegate);
}
#endif
#if BUILDFLAG(IS_IOS)
void CurrentUIThread::Attach() {
current_->AttachToMessagePump();
}
#endif // BUILDFLAG(IS_IOS)
#if BUILDFLAG(IS_ANDROID)
void CurrentUIThread::Abort() {
GetMessagePumpForUI()->Abort();
}
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_WIN)
void CurrentUIThread::AddMessagePumpObserver(
MessagePumpForUI::Observer* observer) {
GetMessagePumpForUI()->AddObserver(observer);
}
void CurrentUIThread::RemoveMessagePumpObserver(
MessagePumpForUI::Observer* observer) {
GetMessagePumpForUI()->RemoveObserver(observer);
}
#endif // BUILDFLAG(IS_WIN)
#endif // !BUILDFLAG(IS_NACL)
//------------------------------------------------------------------------------
// CurrentIOThread
// static
CurrentIOThread CurrentIOThread::Get() {
auto* sequence_manager = GetCurrentSequenceManagerImpl();
DCHECK(sequence_manager);
DCHECK(sequence_manager->IsType(MessagePumpType::IO));
return CurrentIOThread(sequence_manager);
}
// static
bool CurrentIOThread::IsSet() {
auto* sequence_manager = GetCurrentSequenceManagerImpl();
return sequence_manager && sequence_manager->IsType(MessagePumpType::IO);
}
MessagePumpForIO* CurrentIOThread::GetMessagePumpForIO() const {
return static_cast<MessagePumpForIO*>(current_->GetMessagePump());
}
#if !BUILDFLAG(IS_NACL)
#if BUILDFLAG(IS_WIN)
bool CurrentIOThread::RegisterIOHandler(HANDLE file,
MessagePumpForIO::IOHandler* handler) {
DCHECK(current_->IsBoundToCurrentThread());
return GetMessagePumpForIO()->RegisterIOHandler(file, handler);
}
bool CurrentIOThread::RegisterJobObject(HANDLE job,
MessagePumpForIO::IOHandler* handler) {
DCHECK(current_->IsBoundToCurrentThread());
return GetMessagePumpForIO()->RegisterJobObject(job, handler);
}
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
bool CurrentIOThread::WatchFileDescriptor(
int fd,
bool persistent,
MessagePumpForIO::Mode mode,
MessagePumpForIO::FdWatchController* controller,
MessagePumpForIO::FdWatcher* delegate) {
DCHECK(current_->IsBoundToCurrentThread());
return GetMessagePumpForIO()->WatchFileDescriptor(fd, persistent, mode,
controller, delegate);
}
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_MAC) || (BUILDFLAG(IS_IOS) && !BUILDFLAG(CRONET_BUILD))
bool CurrentIOThread::WatchMachReceivePort(
mach_port_t port,
MessagePumpForIO::MachPortWatchController* controller,
MessagePumpForIO::MachPortWatcher* delegate) {
DCHECK(current_->IsBoundToCurrentThread());
return GetMessagePumpForIO()->WatchMachReceivePort(port, controller,
delegate);
}
#endif
#endif // !BUILDFLAG(IS_NACL)
#if BUILDFLAG(IS_FUCHSIA)
// Additional watch API for native platform resources.
bool CurrentIOThread::WatchZxHandle(
zx_handle_t handle,
bool persistent,
zx_signals_t signals,
MessagePumpForIO::ZxHandleWatchController* controller,
MessagePumpForIO::ZxHandleWatcher* delegate) {
DCHECK(current_->IsBoundToCurrentThread());
return GetMessagePumpForIO()->WatchZxHandle(handle, persistent, signals,
controller, delegate);
}
#endif
} // namespace base

View File

@@ -0,0 +1,350 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_CURRENT_THREAD_H_
#define BASE_TASK_CURRENT_THREAD_H_
#include <ostream>
#include <type_traits>
#include "base/base_export.h"
#include "base/callback_list.h"
#include "base/check.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/ios_cronet_buildflags.h"
#include "base/message_loop/message_pump_for_io.h"
#include "base/message_loop/message_pump_for_ui.h"
#include "base/pending_task.h"
#include "base/task/sequence_manager/task_time_observer.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_observer.h"
#include "build/build_config.h"
namespace autofill {
class NextIdleBarrier;
}
namespace content {
class BrowserMainLoop;
}
namespace web {
class WebTaskEnvironment;
}
namespace base {
namespace test {
bool RunUntil(FunctionRef<bool(void)>);
void TestPredicateOrRegisterOnNextIdleCallback(base::FunctionRef<bool(void)>,
CallbackListSubscription*,
OnceClosure);
} // namespace test
namespace sequence_manager {
namespace internal {
class SequenceManagerImpl;
}
} // namespace sequence_manager
// CurrentThread is a proxy to a subset of Task related APIs bound to the
// current thread
//
// Current(UI|IO)Thread is available statically through
// Current(UI|IO)Thread::Get() on threads that have registered as CurrentThread
// on this physical thread (e.g. by using SingleThreadTaskExecutor). APIs
// intended for all consumers on the thread should be on Current(UI|IO)Thread,
// while internal APIs might be on multiple internal classes (e.g.
// SequenceManager).
//
// Why: Historically MessageLoop would take care of everything related to event
// processing on a given thread. Nowadays that functionality is split among
// different classes. At that time MessageLoop::current() gave access to the
// full MessageLoop API, preventing both addition of powerful owner-only APIs as
// well as making it harder to remove callers of deprecated APIs (that need to
// stick around for a few owner-only use cases and re-accrue callers after
// cleanup per remaining publicly available).
//
// As such, many methods below are flagged as deprecated and should be removed
// once all static callers have been migrated.
class BASE_EXPORT CurrentThread {
public:
// CurrentThread is effectively just a disguised pointer and is fine to
// copy/move around.
CurrentThread(const CurrentThread& other) = default;
CurrentThread(CurrentThread&& other) = default;
CurrentThread& operator=(const CurrentThread& other) = default;
friend bool operator==(const CurrentThread&, const CurrentThread&) = default;
// Returns a proxy object to interact with the Task related APIs for the
// current thread. It must only be used on the thread it was obtained.
static CurrentThread Get();
// Return an empty CurrentThread. No methods should be called on this
// object.
static CurrentThread GetNull();
// Returns true if the current thread is registered to expose CurrentThread
// API. Prefer this to verifying the boolean value of Get() (so that Get() can
// ultimately DCHECK it's only invoked when IsSet()).
static bool IsSet();
// Allow CurrentThread to be used like a pointer to support the many
// callsites that used MessageLoop::current() that way when it was a
// MessageLoop*.
CurrentThread* operator->() { return this; }
explicit operator bool() const { return !!current_; }
// A DestructionObserver is notified when the current task execution
// environment is being destroyed. These observers are notified prior to
// CurrentThread::IsSet() being changed to return false. This gives interested
// parties the chance to do final cleanup.
//
// NOTE: Any tasks posted to the current thread during this notification will
// not be run. Instead, they will be deleted.
//
// Deprecation note: Prefer SequenceLocalStorageSlot<std::unique_ptr<Foo>> to
// DestructionObserver to bind an object's lifetime to the current
// thread/sequence.
class BASE_EXPORT DestructionObserver {
public:
// TODO(crbug.com/40596446): Rename to
// WillDestroyCurrentTaskExecutionEnvironment
virtual void WillDestroyCurrentMessageLoop() = 0;
protected:
virtual ~DestructionObserver() = default;
};
// Add a DestructionObserver, which will start receiving notifications
// immediately.
void AddDestructionObserver(DestructionObserver* destruction_observer);
// Remove a DestructionObserver. It is safe to call this method while a
// DestructionObserver is receiving a notification callback.
void RemoveDestructionObserver(DestructionObserver* destruction_observer);
// Forwards to SequenceManager::SetTaskRunner().
// DEPRECATED(https://crbug.com/825327): only owners of the SequenceManager
// instance should replace its TaskRunner.
void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
// Forwards to SequenceManager::(Add|Remove)TaskObserver.
// DEPRECATED(https://crbug.com/825327): only owners of the SequenceManager
// instance should add task observers on it.
void AddTaskObserver(TaskObserver* task_observer);
void RemoveTaskObserver(TaskObserver* task_observer);
// When this functionality is enabled, the queue time will be recorded for
// posted tasks.
void SetAddQueueTimeToTasks(bool enable);
// Registers a `OnceClosure` to be called on this thread the next time it goes
// idle. This is meant for internal usage; callers should use BEST_EFFORT
// tasks instead of this for generic work that needs to wait until quiescence
// to run.
class RegisterOnNextIdleCallbackPasskey {
private:
RegisterOnNextIdleCallbackPasskey() = default;
friend autofill::NextIdleBarrier;
friend content::BrowserMainLoop;
friend bool test::RunUntil(FunctionRef<bool(void)>);
friend void test::TestPredicateOrRegisterOnNextIdleCallback(
base::FunctionRef<bool(void)>,
CallbackListSubscription*,
OnceClosure);
};
[[nodiscard]] CallbackListSubscription RegisterOnNextIdleCallback(
RegisterOnNextIdleCallbackPasskey,
OnceClosure on_next_idle_callback);
// Enables nested task processing in scope of an upcoming native message loop.
// Some unwanted message loops may occur when using common controls or printer
// functions. Hence, nested task processing is disabled by default to avoid
// unplanned reentrancy. This re-enables it in cases where the stack is
// reentrancy safe and processing nestable tasks is explicitly safe.
//
// For instance,
// - The current thread is running a message loop.
// - It receives a task #1 and executes it.
// - The task #1 implicitly starts a nested message loop, like a MessageBox in
// the unit test. This can also be StartDoc or GetSaveFileName.
// - The thread receives a task #2 before or while in this second message
// loop.
// - With NestableTasksAllowed set to true, the task #2 will run right away.
// Otherwise, it will get executed right after task #1 completes at "thread
// message loop level".
//
// Use RunLoop::Type::kNestableTasksAllowed when nesting is triggered by the
// application RunLoop rather than by native code.
class BASE_EXPORT ScopedAllowApplicationTasksInNativeNestedLoop {
public:
ScopedAllowApplicationTasksInNativeNestedLoop();
~ScopedAllowApplicationTasksInNativeNestedLoop();
private:
const raw_ptr<sequence_manager::internal::SequenceManagerImpl>
sequence_manager_;
const bool previous_state_;
};
// Returns true if nestable tasks are allowed on the current thread at this
// time (i.e. if a native nested loop would start from the callee's point in
// the stack, would it be allowed to run application tasks).
bool ApplicationTasksAllowedInNativeNestedLoop() const;
// Returns true if this instance is bound to the current thread.
bool IsBoundToCurrentThread() const;
// Returns true if the current thread is idle (ignoring delayed tasks). This
// is the same condition which triggers DoWork() to return false: i.e. out of
// tasks which can be processed at the current run-level -- there might be
// deferred non-nestable tasks remaining if currently in a nested run level.
bool IsIdleForTesting();
// Enables ThreadControllerWithMessagePumpImpl's TimeKeeper metrics.
// `thread_name` will be used as a suffix.
// Setting `wall_time_based_metrics_enabled_for_testing` adds wall-time
// based metrics for this thread. This is only for test environments as it
// disables subsampling.
void EnableMessagePumpTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing = false);
protected:
explicit CurrentThread(
sequence_manager::internal::SequenceManagerImpl* sequence_manager)
: current_(sequence_manager) {}
static sequence_manager::internal::SequenceManagerImpl*
GetCurrentSequenceManagerImpl();
friend class ScheduleWorkTest;
friend class Thread;
friend class sequence_manager::internal::SequenceManagerImpl;
friend class MessageLoopTaskRunnerTest;
friend class web::WebTaskEnvironment;
raw_ptr<sequence_manager::internal::SequenceManagerImpl> current_;
};
#if !BUILDFLAG(IS_NACL)
// UI extension of CurrentThread.
class BASE_EXPORT CurrentUIThread : public CurrentThread {
public:
// Returns an interface for the CurrentUIThread of the current thread.
// Asserts that IsSet().
static CurrentUIThread Get();
// Returns true if the current thread is running a CurrentUIThread.
static bool IsSet();
CurrentUIThread* operator->() { return this; }
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_WIN)
static_assert(
std::is_base_of_v<WatchableIOMessagePumpPosix, MessagePumpForUI>,
"CurrentThreadForUI::WatchFileDescriptor is supported only"
"by MessagePumpEpoll and MessagePumpGlib implementations.");
bool WatchFileDescriptor(int fd,
bool persistent,
MessagePumpForUI::Mode mode,
MessagePumpForUI::FdWatchController* controller,
MessagePumpForUI::FdWatcher* delegate);
#endif
#if BUILDFLAG(IS_IOS)
// Forwards to SequenceManager::Attach().
// TODO(crbug.com/40568517): Plumb the actual SequenceManager* to
// callers and remove ability to access this method from
// CurrentUIThread.
void Attach();
#endif
#if BUILDFLAG(IS_ANDROID)
// Forwards to MessagePumpAndroid::Abort().
// TODO(crbug.com/40568517): Plumb the actual MessagePumpForUI* to
// callers and remove ability to access this method from
// CurrentUIThread.
void Abort();
#endif
#if BUILDFLAG(IS_WIN)
void AddMessagePumpObserver(MessagePumpForUI::Observer* observer);
void RemoveMessagePumpObserver(MessagePumpForUI::Observer* observer);
#endif
private:
explicit CurrentUIThread(
sequence_manager::internal::SequenceManagerImpl* current)
: CurrentThread(current) {}
MessagePumpForUI* GetMessagePumpForUI() const;
};
#endif // !BUILDFLAG(IS_NACL)
// ForIO extension of CurrentThread.
class BASE_EXPORT CurrentIOThread : public CurrentThread {
public:
// Returns an interface for the CurrentIOThread of the current thread.
// Asserts that IsSet().
static CurrentIOThread Get();
// Returns true if the current thread is running a CurrentIOThread.
static bool IsSet();
CurrentIOThread* operator->() { return this; }
#if !BUILDFLAG(IS_NACL)
#if BUILDFLAG(IS_WIN)
// Please see MessagePumpWin for definitions of these methods.
[[nodiscard]] bool RegisterIOHandler(HANDLE file,
MessagePumpForIO::IOHandler* handler);
bool RegisterJobObject(HANDLE job, MessagePumpForIO::IOHandler* handler);
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
// Please see WatchableIOMessagePumpPosix for definition.
// Prefer base::FileDescriptorWatcher for non-critical IO.
bool WatchFileDescriptor(int fd,
bool persistent,
MessagePumpForIO::Mode mode,
MessagePumpForIO::FdWatchController* controller,
MessagePumpForIO::FdWatcher* delegate);
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_MAC) || (BUILDFLAG(IS_IOS) && !BUILDFLAG(CRONET_BUILD))
bool WatchMachReceivePort(
mach_port_t port,
MessagePumpForIO::MachPortWatchController* controller,
MessagePumpForIO::MachPortWatcher* delegate);
#endif
#if BUILDFLAG(IS_FUCHSIA)
// Additional watch API for native platform resources.
bool WatchZxHandle(zx_handle_t handle,
bool persistent,
zx_signals_t signals,
MessagePumpForIO::ZxHandleWatchController* controller,
MessagePumpForIO::ZxHandleWatcher* delegate);
#endif // BUILDFLAG(IS_FUCHSIA)
#endif // !BUILDFLAG(IS_NACL)
private:
explicit CurrentIOThread(
sequence_manager::internal::SequenceManagerImpl* current)
: CurrentThread(current) {}
MessagePumpForIO* GetMessagePumpForIO() const;
};
} // namespace base
#endif // BASE_TASK_CURRENT_THREAD_H_

View File

@@ -0,0 +1,39 @@
// Copyright 2021 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/default_delayed_task_handle_delegate.h"
#include <utility>
#include "base/functional/bind.h"
namespace base {
DefaultDelayedTaskHandleDelegate::DefaultDelayedTaskHandleDelegate() = default;
DefaultDelayedTaskHandleDelegate::~DefaultDelayedTaskHandleDelegate() = default;
bool DefaultDelayedTaskHandleDelegate::IsValid() const {
return weak_ptr_factory_.HasWeakPtrs();
}
void DefaultDelayedTaskHandleDelegate::CancelTask() {
weak_ptr_factory_.InvalidateWeakPtrs();
}
OnceClosure DefaultDelayedTaskHandleDelegate::BindCallback(
OnceClosure callback) {
DCHECK(!IsValid());
return BindOnce(&DefaultDelayedTaskHandleDelegate::RunTask,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
}
void DefaultDelayedTaskHandleDelegate::RunTask(OnceClosure user_task) {
// Invalidate the weak pointer first so that the task handle is considered
// invalid while running the task.
weak_ptr_factory_.InvalidateWeakPtrs();
std::move(user_task).Run();
}
} // namespace base

View File

@@ -0,0 +1,40 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_DEFAULT_DELAYED_TASK_HANDLE_DELEGATE_H_
#define BASE_TASK_DEFAULT_DELAYED_TASK_HANDLE_DELEGATE_H_
#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/task/delayed_task_handle.h"
namespace base {
// A default implementation of DelayedTaskHandle::Delegate that can cancel the
// delayed task by invalidating a weak pointer.
class BASE_EXPORT DefaultDelayedTaskHandleDelegate
: public DelayedTaskHandle::Delegate {
public:
DefaultDelayedTaskHandleDelegate();
~DefaultDelayedTaskHandleDelegate() override;
// DelayedTaskHandle::Delegate:
bool IsValid() const override;
void CancelTask() override;
// Returns a new callback bound to this object such that it can be cancelled
// by invalidating |weak_ptr_factory_|.
OnceClosure BindCallback(OnceClosure callback);
private:
// Runs |callback|.
void RunTask(OnceClosure callback);
WeakPtrFactory<DefaultDelayedTaskHandleDelegate> weak_ptr_factory_{this};
};
} // namespace base
#endif // BASE_TASK_DEFAULT_DELAYED_TASK_HANDLE_DELEGATE_H_

View File

@@ -0,0 +1,147 @@
// Copyright 2013 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/deferred_sequenced_task_runner.h"
#include "base/task/common/scoped_defer_task_posting.h"
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
namespace base {
DeferredSequencedTaskRunner::DeferredTask::DeferredTask()
: is_non_nestable(false) {
}
DeferredSequencedTaskRunner::DeferredTask::DeferredTask(DeferredTask&& other) =
default;
DeferredSequencedTaskRunner::DeferredTask::~DeferredTask() = default;
DeferredSequencedTaskRunner::DeferredTask&
DeferredSequencedTaskRunner::DeferredTask::operator=(DeferredTask&& other) =
default;
DeferredSequencedTaskRunner::DeferredSequencedTaskRunner(
scoped_refptr<SequencedTaskRunner> target_task_runner)
: created_thread_id_(PlatformThread::CurrentId()),
target_task_runner_(std::move(target_task_runner)) {
#if DCHECK_IS_ON()
AutoLock lock(lock_);
DCHECK(target_task_runner_);
#endif
task_runner_atomic_ptr_.store(target_task_runner_.get(),
std::memory_order_release);
}
DeferredSequencedTaskRunner::DeferredSequencedTaskRunner()
: created_thread_id_(PlatformThread::CurrentId()) {}
bool DeferredSequencedTaskRunner::PostDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) {
// Do not process new PostTasks while we are handling a PostTask (tracing
// has to do this) as it can lead to a deadlock and defer it instead.
ScopedDeferTaskPosting disallow_task_posting;
AutoLock lock(lock_);
if (started_) {
DCHECK(deferred_tasks_queue_.empty());
return target_task_runner_->PostDelayedTask(from_here, std::move(task),
delay);
}
QueueDeferredTask(from_here, std::move(task), delay,
false /* is_non_nestable */);
return true;
}
bool DeferredSequencedTaskRunner::RunsTasksInCurrentSequence() const {
// task_runner_atomic_ptr_ cannot change once it has been initialized, so it's
// safe to access it without lock.
SequencedTaskRunner* task_runner_ptr =
task_runner_atomic_ptr_.load(std::memory_order_acquire);
if (task_runner_ptr) {
return task_runner_ptr->RunsTasksInCurrentSequence();
}
return created_thread_id_ == PlatformThread::CurrentId();
}
bool DeferredSequencedTaskRunner::PostNonNestableDelayedTask(
const Location& from_here,
OnceClosure task,
TimeDelta delay) {
AutoLock lock(lock_);
if (started_) {
DCHECK(deferred_tasks_queue_.empty());
return target_task_runner_->PostNonNestableDelayedTask(
from_here, std::move(task), delay);
}
QueueDeferredTask(from_here, std::move(task), delay,
true /* is_non_nestable */);
return true;
}
void DeferredSequencedTaskRunner::Start() {
AutoLock lock(lock_);
StartImpl();
}
void DeferredSequencedTaskRunner::StartWithTaskRunner(
scoped_refptr<SequencedTaskRunner> target_task_runner) {
AutoLock lock(lock_);
DCHECK(!target_task_runner_);
DCHECK(target_task_runner);
target_task_runner_ = std::move(target_task_runner);
task_runner_atomic_ptr_.store(target_task_runner_.get(),
std::memory_order_release);
StartImpl();
}
bool DeferredSequencedTaskRunner::Started() const {
AutoLock lock(lock_);
return started_;
}
DeferredSequencedTaskRunner::~DeferredSequencedTaskRunner() = default;
void DeferredSequencedTaskRunner::QueueDeferredTask(const Location& from_here,
OnceClosure task,
TimeDelta delay,
bool is_non_nestable) {
lock_.AssertAcquired();
// Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
// for details.
CHECK(task);
DeferredTask deferred_task;
deferred_task.posted_from = from_here;
deferred_task.task = std::move(task);
deferred_task.delay = delay;
deferred_task.is_non_nestable = is_non_nestable;
deferred_tasks_queue_.push_back(std::move(deferred_task));
}
void DeferredSequencedTaskRunner::StartImpl() {
lock_.AssertAcquired(); // Callers should have grabbed the lock.
DCHECK(!started_);
started_ = true;
DCHECK(target_task_runner_);
for (auto& task : deferred_tasks_queue_) {
if (task.is_non_nestable) {
target_task_runner_->PostNonNestableDelayedTask(
task.posted_from, std::move(task.task), task.delay);
} else {
target_task_runner_->PostDelayedTask(task.posted_from,
std::move(task.task), task.delay);
}
}
deferred_tasks_queue_.clear();
}
} // namespace base

View File

@@ -0,0 +1,103 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_DEFERRED_SEQUENCED_TASK_RUNNER_H_
#define BASE_TASK_DEFERRED_SEQUENCED_TASK_RUNNER_H_
#include <vector>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/functional/callback.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
namespace base {
// A DeferredSequencedTaskRunner is a subclass of SequencedTaskRunner that
// queues up all requests until the first call to Start() is issued.
// DeferredSequencedTaskRunner may be created in two ways:
// . with an explicit SequencedTaskRunner that the events are flushed to
// . without a SequencedTaskRunner. In this configuration the
// SequencedTaskRunner is supplied in StartWithTaskRunner().
class BASE_EXPORT DeferredSequencedTaskRunner : public SequencedTaskRunner {
public:
explicit DeferredSequencedTaskRunner(
scoped_refptr<SequencedTaskRunner> target_runner);
// Use this constructor when you don't have the target SequencedTaskRunner.
// When using this call StartWithTaskRunner().
DeferredSequencedTaskRunner();
DeferredSequencedTaskRunner(const DeferredSequencedTaskRunner&) = delete;
DeferredSequencedTaskRunner& operator=(const DeferredSequencedTaskRunner&) =
delete;
// TaskRunner implementation
bool PostDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override;
// SequencedTaskRunner implementation
bool RunsTasksInCurrentSequence() const override;
bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override;
// Start the execution - posts all queued tasks to the target executor. The
// deferred tasks are posted with their initial delay, meaning that the task
// execution delay is actually measured from Start.
// Fails when called a second time.
void Start();
// Same as Start(), but must be used with the no-arg constructor.
void StartWithTaskRunner(
scoped_refptr<SequencedTaskRunner> target_task_runner);
// Returns true if task execution has been started.
bool Started() const;
private:
struct DeferredTask {
DeferredTask();
DeferredTask(DeferredTask&& other);
~DeferredTask();
DeferredTask& operator=(DeferredTask&& other);
Location posted_from;
OnceClosure task;
// The delay this task was initially posted with.
TimeDelta delay;
bool is_non_nestable;
};
~DeferredSequencedTaskRunner() override;
// Both variants of Start() call into this.
void StartImpl();
// Creates a |Task| object and adds it to |deferred_tasks_queue_|.
void QueueDeferredTask(const Location& from_here,
OnceClosure task,
TimeDelta delay,
bool is_non_nestable);
mutable Lock lock_;
const PlatformThreadId created_thread_id_;
// An atomic pointer that allows to call task_runner methods without lock.
// It's possible because the pointer starts as null, is set to a non-null
// value only once, and is never changed again.
// This is used to implement a lock-free RunsTasksInCurrentSequence method.
std::atomic<SequencedTaskRunner*> task_runner_atomic_ptr_{nullptr};
bool started_ GUARDED_BY(lock_) = false;
scoped_refptr<SequencedTaskRunner> target_task_runner_ GUARDED_BY(lock_);
std::vector<DeferredTask> deferred_tasks_queue_ GUARDED_BY(lock_);
};
} // namespace base
#endif // BASE_TASK_DEFERRED_SEQUENCED_TASK_RUNNER_H_

View File

@@ -0,0 +1,43 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_DELAY_POLICY_H_
#define BASE_TASK_DELAY_POLICY_H_
#include "base/time/time.h"
namespace base {
namespace subtle {
// Policies affecting how a delayed task is scheduled when a TimeTicks is
// specified.
enum class DelayPolicy {
// A delayed task with kFlexibleNoSooner may not run any sooner than the
// specified time, but might run slightly after. This is the behavior implied
// by PostDelayedTask.
kFlexibleNoSooner,
// A delayed task with kFlexiblePreferEarly means the task should attempt to
// run near the deadline and preferably a little bit before than after if the
// scheduler applies slack.
kFlexiblePreferEarly,
// A delayed task with kPrecise means it may not run any sooner than the
// specified time and preferably as close as possible to the specified time,
// which may affect scheduling policies if the scheduler usually applies
// slack.
kPrecise,
};
inline DelayPolicy MaybeOverrideDelayPolicy(DelayPolicy delay_policy,
TimeDelta delay,
TimeDelta max_precise_delay) {
if (delay >= max_precise_delay && delay_policy == DelayPolicy::kPrecise) {
return DelayPolicy::kFlexibleNoSooner;
}
return delay_policy;
}
} // namespace subtle
} // namespace base
#endif // BASE_TASK_DELAY_POLICY_H_

View File

@@ -0,0 +1,48 @@
// Copyright 2021 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/delayed_task_handle.h"
#include <utility>
#include "base/check.h"
namespace base {
DelayedTaskHandle::DelayedTaskHandle() = default;
DelayedTaskHandle::DelayedTaskHandle(std::unique_ptr<Delegate> delegate)
: delegate_(std::move(delegate)) {
DCHECK(IsValid());
}
DelayedTaskHandle::~DelayedTaskHandle() {
// A task handle should never be destroyed in a valid state. It should either
// have been executed, canceled or have had its task deleted.
DCHECK(!IsValid());
}
DelayedTaskHandle::DelayedTaskHandle(DelayedTaskHandle&& other) = default;
DelayedTaskHandle& DelayedTaskHandle::operator=(DelayedTaskHandle&& other) {
// A valid handle can't be overwritten by an assignment.
DCHECK(!IsValid());
delegate_ = std::move(other.delegate_);
return *this;
}
bool DelayedTaskHandle::IsValid() const {
return delegate_ && delegate_->IsValid();
}
void DelayedTaskHandle::CancelTask() {
// The delegate is responsible for cancelling the task.
if (delegate_) {
delegate_->CancelTask();
DCHECK(!delegate_->IsValid());
delegate_.reset();
}
}
} // namespace base

View File

@@ -0,0 +1,56 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_DELAYED_TASK_HANDLE_H_
#define BASE_TASK_DELAYED_TASK_HANDLE_H_
#include <memory>
#include "base/base_export.h"
namespace base {
// A handle to a delayed task which can be used to cancel the posted task. Not
// thread-safe, can only be held and invoked from the posting sequence.
class BASE_EXPORT DelayedTaskHandle {
public:
// The delegate that allows each SequencedTaskRunners to have different
// implementations.
class Delegate {
public:
virtual ~Delegate() = default;
// Returns true if the task handle is valid. Canceling or running the task
// will mark it as invalid.
virtual bool IsValid() const = 0;
// Cancels the task. A canceled task, whether removed from the underlying
// queue or only marked as canceled, will never be Run().
virtual void CancelTask() = 0;
};
// Construct a default, invalid, task handle.
DelayedTaskHandle();
// Construct a valid task handle with the specified |delegate|.
explicit DelayedTaskHandle(std::unique_ptr<Delegate> delegate);
~DelayedTaskHandle();
DelayedTaskHandle(DelayedTaskHandle&&);
DelayedTaskHandle& operator=(DelayedTaskHandle&&);
// Returns true if the task handle is valid.
bool IsValid() const;
// Cancels the task.
void CancelTask();
private:
std::unique_ptr<Delegate> delegate_;
};
} // namespace base
#endif // BASE_TASK_DELAYED_TASK_HANDLE_H_

View File

@@ -0,0 +1,127 @@
// Copyright 2017 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/lazy_thread_pool_task_runner.h"
#include <atomic>
#include <utility>
#include "base/check_op.h"
#include "base/lazy_instance_helpers.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
namespace base {
namespace internal {
namespace {
ScopedLazyTaskRunnerListForTesting* g_scoped_lazy_task_runner_list_for_testing =
nullptr;
} // namespace
template <typename TaskRunnerType, bool com_sta>
void LazyThreadPoolTaskRunner<TaskRunnerType, com_sta>::Reset() {
uintptr_t state = state_.load(std::memory_order_acquire);
DCHECK_NE(state, kLazyInstanceStateCreating) << "Race: all threads should be "
"unwound in unittests before "
"resetting TaskRunners.";
// Return if no reference is held by this instance.
if (!state)
return;
// Release the reference acquired in Get().
SequencedTaskRunner* task_runner = reinterpret_cast<TaskRunnerType*>(state);
task_runner->Release();
// Clear the state.
state_.store(0, std::memory_order_relaxed);
}
template <>
scoped_refptr<SequencedTaskRunner>
LazyThreadPoolTaskRunner<SequencedTaskRunner, false>::Create() {
// It is invalid to specify a SingleThreadTaskRunnerThreadMode with a
// LazyThreadPoolSequencedTaskRunner.
DCHECK_EQ(thread_mode_, SingleThreadTaskRunnerThreadMode::SHARED);
return ThreadPool::CreateSequencedTaskRunner(traits_);
}
template <>
scoped_refptr<SingleThreadTaskRunner>
LazyThreadPoolTaskRunner<SingleThreadTaskRunner, false>::Create() {
return ThreadPool::CreateSingleThreadTaskRunner(traits_, thread_mode_);
}
#if BUILDFLAG(IS_WIN)
template <>
scoped_refptr<SingleThreadTaskRunner>
LazyThreadPoolTaskRunner<SingleThreadTaskRunner, true>::Create() {
return ThreadPool::CreateCOMSTATaskRunner(traits_, thread_mode_);
}
#endif
// static
template <typename TaskRunnerType, bool com_sta>
TaskRunnerType* LazyThreadPoolTaskRunner<TaskRunnerType, com_sta>::CreateRaw(
void* void_self) {
auto self =
reinterpret_cast<LazyThreadPoolTaskRunner<TaskRunnerType, com_sta>*>(
void_self);
scoped_refptr<TaskRunnerType> task_runner = self->Create();
// Acquire a reference to the TaskRunner. The reference will either
// never be released or be released in Reset(). The reference is not
// managed by a scoped_refptr because adding a scoped_refptr member to
// LazyThreadPoolTaskRunner would prevent its static initialization.
task_runner->AddRef();
// Reset this instance when the current
// ScopedLazyTaskRunnerListForTesting is destroyed, if any.
if (g_scoped_lazy_task_runner_list_for_testing) {
g_scoped_lazy_task_runner_list_for_testing->AddCallback(
BindOnce(&LazyThreadPoolTaskRunner<TaskRunnerType, com_sta>::Reset,
Unretained(self)));
}
return task_runner.get();
}
template <typename TaskRunnerType, bool com_sta>
scoped_refptr<TaskRunnerType>
LazyThreadPoolTaskRunner<TaskRunnerType, com_sta>::Get() {
return WrapRefCounted(subtle::GetOrCreateLazyPointer(
state_, &LazyThreadPoolTaskRunner<TaskRunnerType, com_sta>::CreateRaw,
reinterpret_cast<void*>(this), nullptr, nullptr));
}
template class LazyThreadPoolTaskRunner<SequencedTaskRunner, false>;
template class LazyThreadPoolTaskRunner<SingleThreadTaskRunner, false>;
#if BUILDFLAG(IS_WIN)
template class LazyThreadPoolTaskRunner<SingleThreadTaskRunner, true>;
#endif
ScopedLazyTaskRunnerListForTesting::ScopedLazyTaskRunnerListForTesting() {
DCHECK(!g_scoped_lazy_task_runner_list_for_testing);
g_scoped_lazy_task_runner_list_for_testing = this;
}
ScopedLazyTaskRunnerListForTesting::~ScopedLazyTaskRunnerListForTesting() {
internal::CheckedAutoLock auto_lock(lock_);
for (auto& callback : callbacks_)
std::move(callback).Run();
g_scoped_lazy_task_runner_list_for_testing = nullptr;
}
void ScopedLazyTaskRunnerListForTesting::AddCallback(OnceClosure callback) {
internal::CheckedAutoLock auto_lock(lock_);
callbacks_.push_back(std::move(callback));
}
} // namespace internal
} // namespace base

View File

@@ -0,0 +1,215 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_LAZY_THREAD_POOL_TASK_RUNNER_H_
#define BASE_TASK_LAZY_THREAD_POOL_TASK_RUNNER_H_
#include <atomic>
#include <vector>
#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/macros/uniquify.h"
#include "base/task/common/checked_lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/single_thread_task_runner_thread_mode.h"
#include "base/task/task_traits.h"
#include "base/thread_annotations.h"
#include "build/build_config.h"
// Lazy(Sequenced|SingleThread|COMSTA)TaskRunner lazily creates a TaskRunner.
//
// Lazy(Sequenced|SingleThread|COMSTA)TaskRunner is meant to be instantiated in
// an anonymous namespace (no static initializer is generated) and used to post
// tasks to the same thread-pool-bound sequence/thread from pieces of code that
// don't have a better way of sharing a TaskRunner. It is important to use this
// class instead of a self-managed global variable or LazyInstance so that the
// TaskRunners do not outlive the scope of the TaskEnvironment in unit tests
// (otherwise the next test in the same process will die in use-after-frees).
//
// IMPORTANT: Only use this API as a last resort. Prefer storing a
// (Sequenced|SingleThread)TaskRunner returned by
// base::ThreadPool::Create(Sequenced|SingleThread|COMSTA)TaskRunner() as a
// member on an object accessible by all PostTask() call sites.
//
// Example usage 1:
//
// namespace {
// base::LazyThreadPoolSequencedTaskRunner g_sequenced_task_runner =
// LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(
// base::TaskTraits(base::MayBlock(),
// base::TaskPriority::USER_VISIBLE));
// } // namespace
//
// void SequencedFunction() {
// // Different invocations of this function post to the same
// // MayBlock() SequencedTaskRunner.
// g_sequenced_task_runner.Get()->PostTask(FROM_HERE, base::BindOnce(...));
// }
//
// Example usage 2:
//
// namespace {
// base::LazyThreadPoolSequencedTaskRunner g_sequenced_task_task_runner =
// LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(
// base::TaskTraits(base::MayBlock()));
// } // namespace
//
// // Code from different files can access the SequencedTaskRunner via this
// // function.
// scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() {
// return g_sequenced_task_runner.Get();
// }
namespace base {
namespace internal {
template <typename TaskRunnerType, bool com_sta>
class BASE_EXPORT LazyThreadPoolTaskRunner;
} // namespace internal
// Lazy SequencedTaskRunner.
using LazyThreadPoolSequencedTaskRunner =
internal::LazyThreadPoolTaskRunner<SequencedTaskRunner, false>;
// Lazy SingleThreadTaskRunner.
using LazyThreadPoolSingleThreadTaskRunner =
internal::LazyThreadPoolTaskRunner<SingleThreadTaskRunner, false>;
#if BUILDFLAG(IS_WIN)
// Lazy COM-STA enabled SingleThreadTaskRunner.
using LazyThreadPoolCOMSTATaskRunner =
internal::LazyThreadPoolTaskRunner<SingleThreadTaskRunner, true>;
#endif
// Use the macros below to initialize a LazyThreadPoolTaskRunner. These macros
// verify that their arguments are constexpr, which is important to prevent the
// generation of a static initializer.
// |traits| are TaskTraits used when creating the SequencedTaskRunner.
#define LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(traits) \
base::LazyThreadPoolSequencedTaskRunner::CreateInternal(traits); \
[[maybe_unused]] constexpr base::TaskTraits BASE_UNIQUIFY( \
kVerifyTraitsAreConstexpr) = traits
// |traits| are TaskTraits used when creating the SingleThreadTaskRunner.
// |thread_mode| specifies whether the SingleThreadTaskRunner can share its
// thread with other SingleThreadTaskRunners.
#define LAZY_THREAD_POOL_SINGLE_THREAD_TASK_RUNNER_INITIALIZER(traits, \
thread_mode) \
base::LazyThreadPoolSingleThreadTaskRunner::CreateInternal(traits, \
thread_mode); \
[[maybe_unused]] constexpr base::TaskTraits BASE_UNIQUIFY( \
kVerifyTraitsAreConstexpr) = traits; \
[[maybe_unused]] constexpr base::SingleThreadTaskRunnerThreadMode \
BASE_UNIQUIFY(kVerifyThreadModeIsConstexpr) = thread_mode
// |traits| are TaskTraits used when creating the COM STA
// SingleThreadTaskRunner. |thread_mode| specifies whether the COM STA
// SingleThreadTaskRunner can share its thread with other
// SingleThreadTaskRunners.
#define LAZY_COM_STA_TASK_RUNNER_INITIALIZER(traits, thread_mode) \
base::LazyThreadPoolCOMSTATaskRunner::CreateInternal(traits, thread_mode); \
[[maybe_unused]] constexpr base::TaskTraits BASE_UNIQUIFY( \
kVerifyTraitsAreConstexpr) = traits; \
[[maybe_unused]] constexpr base::SingleThreadTaskRunnerThreadMode \
BASE_UNIQUIFY(kVerifyThreadModeIsConstexpr) = thread_mode
namespace internal {
template <typename TaskRunnerType, bool com_sta>
class BASE_EXPORT LazyThreadPoolTaskRunner {
public:
// Use the macros above rather than a direct call to this.
//
// |traits| are TaskTraits to use to create the TaskRunner. If this
// LazyThreadPoolTaskRunner is specialized to create a SingleThreadTaskRunner,
// |thread_mode| specifies whether the SingleThreadTaskRunner can share its
// thread with other SingleThreadTaskRunner. Otherwise, it is unused.
static constexpr LazyThreadPoolTaskRunner CreateInternal(
const TaskTraits& traits,
SingleThreadTaskRunnerThreadMode thread_mode =
SingleThreadTaskRunnerThreadMode::SHARED) {
return LazyThreadPoolTaskRunner(traits, thread_mode);
}
// Returns the TaskRunner held by this instance. Creates it if it didn't
// already exist. Thread-safe.
scoped_refptr<TaskRunnerType> Get();
private:
constexpr LazyThreadPoolTaskRunner(
const TaskTraits& traits,
SingleThreadTaskRunnerThreadMode thread_mode =
SingleThreadTaskRunnerThreadMode::SHARED)
: traits_(traits), thread_mode_(thread_mode) {}
// Releases the TaskRunner held by this instance.
void Reset();
// Creates and returns a new TaskRunner.
scoped_refptr<TaskRunnerType> Create();
// Creates a new TaskRunner via Create(), adds an explicit ref to it, and
// returns it raw. Used as an adapter for lazy instance helpers. Static and
// takes |this| as an explicit param to match the void* signature of
// GetOrCreateLazyPointer().
static TaskRunnerType* CreateRaw(void* void_self);
// TaskTraits to create the TaskRunner.
const TaskTraits traits_;
// SingleThreadTaskRunnerThreadMode to create the TaskRunner.
const SingleThreadTaskRunnerThreadMode thread_mode_;
// Can have 3 states:
// - This instance does not hold a TaskRunner: 0
// - This instance is creating a TaskRunner: kLazyInstanceStateCreating
// - This instance holds a TaskRunner: Pointer to the TaskRunner.
// LazyInstance's internals are reused to handle transition between states.
std::atomic<uintptr_t> state_ = 0;
// No DISALLOW_COPY_AND_ASSIGN since that prevents static initialization with
// Visual Studio (warning C4592: 'symbol will be dynamically initialized
// (implementation limitation))'.
};
// When a LazyThreadPoolTaskRunner becomes active (invokes Get()), it adds a
// callback to the current ScopedLazyTaskRunnerListForTesting, if any.
// Callbacks run when the ScopedLazyTaskRunnerListForTesting is
// destroyed. In a test process, a ScopedLazyTaskRunnerListForTesting
// must be instantiated before any LazyThreadPoolTaskRunner becomes active.
class BASE_EXPORT ScopedLazyTaskRunnerListForTesting {
public:
ScopedLazyTaskRunnerListForTesting();
ScopedLazyTaskRunnerListForTesting(
const ScopedLazyTaskRunnerListForTesting&) = delete;
ScopedLazyTaskRunnerListForTesting& operator=(
const ScopedLazyTaskRunnerListForTesting&) = delete;
~ScopedLazyTaskRunnerListForTesting();
private:
friend class LazyThreadPoolTaskRunner<SequencedTaskRunner, false>;
friend class LazyThreadPoolTaskRunner<SingleThreadTaskRunner, false>;
#if BUILDFLAG(IS_WIN)
friend class LazyThreadPoolTaskRunner<SingleThreadTaskRunner, true>;
#endif
// Add |callback| to the list of callbacks to run on destruction.
void AddCallback(OnceClosure callback);
CheckedLock lock_;
// List of callbacks to run on destruction.
std::vector<OnceClosure> callbacks_ GUARDED_BY(lock_);
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_LAZY_THREAD_POOL_TASK_RUNNER_H_

189
src/base/task/post_job.cc Normal file
View File

@@ -0,0 +1,189 @@
// Copyright 2019 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/post_job.h"
#include "base/task/scoped_set_task_priority_for_current_thread.h"
#include "base/task/thread_pool/job_task_source.h"
#include "base/task/thread_pool/pooled_task_runner_delegate.h"
#include "base/task/thread_pool/thread_pool_impl.h"
#include "base/task/thread_pool/thread_pool_instance.h"
namespace base {
namespace {
scoped_refptr<internal::JobTaskSource> CreateJobTaskSource(
const Location& from_here,
const TaskTraits& traits,
RepeatingCallback<void(JobDelegate*)> worker_task,
MaxConcurrencyCallback max_concurrency_callback) {
DCHECK(ThreadPoolInstance::Get())
<< "Hint: if this is in a unit test, you're likely merely missing a "
"base::test::TaskEnvironment member in your fixture.\n";
return base::MakeRefCounted<internal::JobTaskSource>(
from_here, traits, std::move(worker_task),
std::move(max_concurrency_callback),
static_cast<internal::ThreadPoolImpl*>(ThreadPoolInstance::Get()));
}
} // namespace
JobDelegate::JobDelegate(
internal::JobTaskSource* task_source,
internal::PooledTaskRunnerDelegate* pooled_task_runner_delegate)
: task_source_(task_source),
pooled_task_runner_delegate_(pooled_task_runner_delegate) {
DCHECK(task_source_);
}
JobDelegate::~JobDelegate() {
if (task_id_ != kInvalidTaskId)
task_source_->ReleaseTaskId(task_id_);
}
bool JobDelegate::ShouldYield() {
#if DCHECK_IS_ON()
// ShouldYield() shouldn't be called again after returning true.
DCHECK(!last_should_yield_);
#endif // DCHECK_IS_ON()
const bool should_yield =
task_source_->ShouldYield() ||
(pooled_task_runner_delegate_ &&
pooled_task_runner_delegate_->ShouldYield(task_source_));
#if DCHECK_IS_ON()
last_should_yield_ = should_yield;
#endif // DCHECK_IS_ON()
return should_yield;
}
void JobDelegate::YieldIfNeeded() {
// TODO(crbug.com/40574605): Implement this.
}
void JobDelegate::NotifyConcurrencyIncrease() {
task_source_->NotifyConcurrencyIncrease();
}
uint8_t JobDelegate::GetTaskId() {
if (task_id_ == kInvalidTaskId)
task_id_ = task_source_->AcquireTaskId();
return task_id_;
}
JobHandle::JobHandle() = default;
JobHandle::JobHandle(scoped_refptr<internal::JobTaskSource> task_source)
: task_source_(std::move(task_source)) {}
JobHandle::~JobHandle() {
DCHECK(!task_source_)
<< "The Job must be cancelled, detached or joined before its "
"JobHandle is destroyed.";
}
JobHandle::JobHandle(JobHandle&&) = default;
JobHandle& JobHandle::operator=(JobHandle&& other) {
DCHECK(!task_source_)
<< "The Job must be cancelled, detached or joined before its "
"JobHandle is re-assigned.";
task_source_ = std::move(other.task_source_);
return *this;
}
bool JobHandle::IsActive() const {
return task_source_->IsActive();
}
void JobHandle::UpdatePriority(TaskPriority new_priority) {
if (!internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
task_source_->delegate())) {
return;
}
task_source_->delegate()->UpdateJobPriority(task_source_, new_priority);
}
void JobHandle::NotifyConcurrencyIncrease() {
if (!internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
task_source_->delegate())) {
return;
}
task_source_->NotifyConcurrencyIncrease();
}
void JobHandle::Join() {
DCHECK(internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
task_source_->delegate()));
DCHECK_GE(internal::GetTaskPriorityForCurrentThread(),
task_source_->priority_racy())
<< "Join may not be called on Job with higher priority than the current "
"thread.";
UpdatePriority(internal::GetTaskPriorityForCurrentThread());
if (task_source_->GetRemainingConcurrency() != 0) {
// Make sure the task source is in the queue if not enough workers are
// contributing. This is necessary for CreateJob(...).Join(). This is a
// noop if the task source was already in the queue.
task_source_->delegate()->EnqueueJobTaskSource(task_source_);
}
bool must_run = task_source_->WillJoin();
while (must_run)
must_run = task_source_->RunJoinTask();
// Remove |task_source_| from the ThreadPool to prevent access to
// |max_concurrency_callback| after Join().
task_source_->delegate()->RemoveJobTaskSource(task_source_);
task_source_ = nullptr;
}
void JobHandle::Cancel() {
DCHECK(internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
task_source_->delegate()));
task_source_->Cancel();
bool must_run = task_source_->WillJoin();
DCHECK(!must_run);
// Remove |task_source_| from the ThreadPool to prevent access to
// |max_concurrency_callback| after Join().
task_source_->delegate()->RemoveJobTaskSource(task_source_);
task_source_ = nullptr;
}
void JobHandle::CancelAndDetach() {
task_source_->Cancel();
Detach();
}
void JobHandle::Detach() {
DCHECK(task_source_);
task_source_ = nullptr;
}
JobHandle PostJob(const Location& from_here,
const TaskTraits& traits,
RepeatingCallback<void(JobDelegate*)> worker_task,
MaxConcurrencyCallback max_concurrency_callback) {
auto task_source =
CreateJobTaskSource(from_here, traits, std::move(worker_task),
std::move(max_concurrency_callback));
const bool queued =
static_cast<internal::ThreadPoolImpl*>(ThreadPoolInstance::Get())
->EnqueueJobTaskSource(task_source);
if (queued) {
return internal::JobTaskSource::CreateJobHandle(std::move(task_source));
}
return JobHandle();
}
JobHandle CreateJob(const Location& from_here,
const TaskTraits& traits,
RepeatingCallback<void(JobDelegate*)> worker_task,
MaxConcurrencyCallback max_concurrency_callback) {
auto task_source =
CreateJobTaskSource(from_here, traits, std::move(worker_task),
std::move(max_concurrency_callback));
return internal::JobTaskSource::CreateJobHandle(std::move(task_source));
}
} // namespace base

213
src/base/task/post_job.h Normal file
View File

@@ -0,0 +1,213 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_POST_JOB_H_
#define BASE_TASK_POST_JOB_H_
#include <limits>
#include "base/base_export.h"
#include "base/dcheck_is_on.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/stack_allocated.h"
namespace base {
namespace internal {
class JobTaskSource;
class PooledTaskRunnerDelegate;
}
class TaskTraits;
enum class TaskPriority : uint8_t;
// Delegate that's passed to Job's worker task, providing an entry point to
// communicate with the scheduler. To prevent deadlocks, JobDelegate methods
// should never be called while holding a user lock.
class BASE_EXPORT JobDelegate {
STACK_ALLOCATED();
public:
// A JobDelegate is instantiated for each worker task that is run.
// |task_source| is the task source whose worker task is running with this
// delegate and |pooled_task_runner_delegate| is used by ShouldYield() to
// check whether the pool wants this worker task to yield (null if this worker
// should never yield -- e.g. when the main thread is a worker).
JobDelegate(internal::JobTaskSource* task_source,
internal::PooledTaskRunnerDelegate* pooled_task_runner_delegate);
JobDelegate(const JobDelegate&) = delete;
JobDelegate& operator=(const JobDelegate&) = delete;
~JobDelegate();
// Returns true if this thread *must* return from the worker task on the
// current thread ASAP. Workers should periodically invoke ShouldYield (or
// YieldIfNeeded()) as often as is reasonable.
bool ShouldYield();
// If ShouldYield(), this will pause the current thread (allowing it to be
// replaced in the pool); no-ops otherwise. If it pauses, it will resume and
// return from this call whenever higher priority work completes.
// Prefer ShouldYield() over this (only use YieldIfNeeded() when unwinding
// the stack is not possible).
void YieldIfNeeded();
// Notifies the scheduler that max concurrency was increased, and the number
// of worker should be adjusted accordingly. See PostJob() for more details.
void NotifyConcurrencyIncrease();
// Returns a task_id unique among threads currently running this job, such
// that GetTaskId() < worker count. To achieve this, the same task_id may be
// reused by a different thread after a worker_task returns.
uint8_t GetTaskId();
// Returns true if the current task is called from the thread currently
// running JobHandle::Join().
bool IsJoiningThread() const {
return pooled_task_runner_delegate_ == nullptr;
}
private:
static constexpr uint8_t kInvalidTaskId = std::numeric_limits<uint8_t>::max();
internal::JobTaskSource* task_source_ = nullptr;
internal::PooledTaskRunnerDelegate* pooled_task_runner_delegate_ = nullptr;
uint8_t task_id_ = kInvalidTaskId;
#if DCHECK_IS_ON()
// Value returned by the last call to ShouldYield().
bool last_should_yield_ = false;
#endif
};
// Handle returned when posting a Job. Provides methods to control execution of
// the posted Job. To prevent deadlocks, JobHandle methods should never be
// called while holding a user lock.
class BASE_EXPORT JobHandle {
public:
JobHandle();
JobHandle(const JobHandle&) = delete;
JobHandle& operator=(const JobHandle&) = delete;
// A job must either be joined, canceled or detached before the JobHandle is
// destroyed.
~JobHandle();
JobHandle(JobHandle&&);
JobHandle& operator=(JobHandle&&);
// Returns true if associated with a Job.
explicit operator bool() const { return task_source_ != nullptr; }
// Returns true if there's any work pending or any worker running.
bool IsActive() const;
// Update this Job's priority.
void UpdatePriority(TaskPriority new_priority);
// Notifies the scheduler that max concurrency was increased, and the number
// of workers should be adjusted accordingly. See PostJob() for more details.
void NotifyConcurrencyIncrease();
// Contributes to the job on this thread. Doesn't return until all tasks have
// completed and max concurrency becomes 0. This also promotes this Job's
// priority to be at least as high as the calling thread's priority. When
// called immediately, prefer CreateJob(...).Join() over PostJob(...).Join()
// to avoid having too many workers scheduled for executing the workload.
void Join();
// Forces all existing workers to yield ASAP. Waits until they have all
// returned from the Job's callback before returning.
void Cancel();
// Forces all existing workers to yield ASAP but doesnt wait for them.
// Warning, this is dangerous if the Job's callback is bound to or has access
// to state which may be deleted after this call.
void CancelAndDetach();
// Can be invoked before ~JobHandle() to avoid waiting on the job completing.
void Detach();
private:
friend class internal::JobTaskSource;
explicit JobHandle(scoped_refptr<internal::JobTaskSource> task_source);
scoped_refptr<internal::JobTaskSource> task_source_;
};
// Callback used in PostJob() to control the maximum number of threads calling
// the worker task concurrently.
// Returns the maximum number of threads which may call a job's worker task
// concurrently. |worker_count| is the number of threads currently assigned to
// this job which some callers may need to determine their return value.
using MaxConcurrencyCallback =
RepeatingCallback<size_t(size_t /*worker_count*/)>;
// Posts a repeating |worker_task| with specific |traits| to run in parallel on
// base::ThreadPool.
// Returns a JobHandle associated with the Job, which can be joined, canceled or
// detached.
// ThreadPool APIs, including PostJob() and methods of the returned JobHandle,
// must never be called while holding a lock that could be acquired by
// |worker_task| or |max_concurrency_callback| -- that could result in a
// deadlock. This is because [1] |max_concurrency_callback| may be invoked while
// holding internal ThreadPool lock (A), hence |max_concurrency_callback| can
// only use a lock (B) if that lock is *never* held while calling back into a
// ThreadPool entry point from any thread (A=>B/B=>A deadlock) and [2]
// |worker_task| or |max_concurrency_callback| is invoked synchronously from
// JobHandle::Join() (A=>JobHandle::Join()=>A deadlock).
// To avoid scheduling overhead, |worker_task| should do as much work as
// possible in a loop when invoked, and JobDelegate::ShouldYield() should be
// periodically invoked to conditionally exit and let the scheduler prioritize
// work.
//
// A canonical implementation of |worker_task| looks like:
// void WorkerTask(JobDelegate* job_delegate) {
// while (!job_delegate->ShouldYield()) {
// auto work_item = worker_queue.TakeWorkItem(); // Smallest unit of work.
// if (!work_item)
// return:
// ProcessWork(work_item);
// }
// }
//
// |max_concurrency_callback| controls the maximum number of threads calling
// |worker_task| concurrently. |worker_task| is only invoked if the number of
// threads previously running |worker_task| was less than the value returned by
// |max_concurrency_callback|. In general, |max_concurrency_callback| should
// return the latest number of incomplete work items (smallest unit of work)
// left to processed. JobHandle/JobDelegate::NotifyConcurrencyIncrease() *must*
// be invoked shortly after |max_concurrency_callback| starts returning a value
// larger than previously returned values. This usually happens when new work
// items are added and the API user wants additional threads to invoke
// |worker_task| concurrently. The callbacks may be called concurrently on any
// thread until the job is complete. If the job handle is detached, the
// callbacks may still be called, so they must not access global state that
// could be destroyed.
//
// |traits| requirements:
// - base::ThreadPolicy must be specified if the priority of the task runner
// will ever be increased from BEST_EFFORT.
JobHandle BASE_EXPORT PostJob(const Location& from_here,
const TaskTraits& traits,
RepeatingCallback<void(JobDelegate*)> worker_task,
MaxConcurrencyCallback max_concurrency_callback);
// Creates and returns a JobHandle associated with a Job. Unlike PostJob(), this
// doesn't immediately schedules |worker_task| to run on base::ThreadPool
// workers; the Job is then scheduled by calling either
// NotifyConcurrencyIncrease() or Join().
JobHandle BASE_EXPORT
CreateJob(const Location& from_here,
const TaskTraits& traits,
RepeatingCallback<void(JobDelegate*)> worker_task,
MaxConcurrencyCallback max_concurrency_callback);
} // namespace base
#endif // BASE_TASK_POST_JOB_H_

View File

@@ -0,0 +1,38 @@
// 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.
#ifndef BASE_TASK_POST_TASK_AND_REPLY_WITH_RESULT_INTERNAL_H_
#define BASE_TASK_POST_TASK_AND_REPLY_WITH_RESULT_INTERNAL_H_
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/functional/callback.h"
namespace base {
namespace internal {
// Adapts a function that produces a result via a return value to
// one that returns via an output parameter.
template <typename ReturnType>
void ReturnAsParamAdapter(OnceCallback<ReturnType()> func,
std::unique_ptr<ReturnType>* result) {
result->reset(new ReturnType(std::move(func).Run()));
}
// Adapts a T* result to a callblack that expects a T.
template <typename TaskReturnType, typename ReplyArgType>
void ReplyAdapter(OnceCallback<void(ReplyArgType)> callback,
std::unique_ptr<TaskReturnType>* result) {
DCHECK(result->get());
std::move(callback).Run(std::move(**result));
}
} // namespace internal
} // namespace base
#endif // BASE_TASK_POST_TASK_AND_REPLY_WITH_RESULT_INTERNAL_H_

View File

@@ -0,0 +1,38 @@
// 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/scoped_set_task_priority_for_current_thread.h"
#include "base/compiler_specific.h"
namespace base {
namespace internal {
namespace {
constinit thread_local TaskPriority task_priority_for_current_thread =
TaskPriority::USER_BLOCKING;
} // namespace
ScopedSetTaskPriorityForCurrentThread::ScopedSetTaskPriorityForCurrentThread(
TaskPriority priority)
: resetter_(&task_priority_for_current_thread,
priority,
TaskPriority::USER_BLOCKING) {}
ScopedSetTaskPriorityForCurrentThread::
~ScopedSetTaskPriorityForCurrentThread() = default;
TaskPriority GetTaskPriorityForCurrentThread() {
// Workaround false-positive MSAN use-of-uninitialized-value on
// thread_local storage for loaded libraries:
// https://github.com/google/sanitizers/issues/1265
MSAN_UNPOISON(&task_priority_for_current_thread, sizeof(TaskPriority));
return task_priority_for_current_thread;
}
} // namespace internal
} // namespace base

View File

@@ -0,0 +1,40 @@
// 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.
#ifndef BASE_TASK_SCOPED_SET_TASK_PRIORITY_FOR_CURRENT_THREAD_H_
#define BASE_TASK_SCOPED_SET_TASK_PRIORITY_FOR_CURRENT_THREAD_H_
#include "base/auto_reset.h"
#include "base/base_export.h"
#include "base/task/task_traits.h"
namespace base {
namespace internal {
class BASE_EXPORT
[[maybe_unused, nodiscard]] ScopedSetTaskPriorityForCurrentThread {
public:
// Within the scope of this object, GetTaskPriorityForCurrentThread() will
// return |priority|.
explicit ScopedSetTaskPriorityForCurrentThread(TaskPriority priority);
ScopedSetTaskPriorityForCurrentThread(
const ScopedSetTaskPriorityForCurrentThread&) = delete;
ScopedSetTaskPriorityForCurrentThread& operator=(
const ScopedSetTaskPriorityForCurrentThread&) = delete;
~ScopedSetTaskPriorityForCurrentThread();
private:
const AutoReset<TaskPriority> resetter_;
};
// Returns the priority of the task running on the current thread,
// or TaskPriority::USER_BLOCKING by default if none.
BASE_EXPORT TaskPriority GetTaskPriorityForCurrentThread();
} // namespace internal
} // namespace base
#endif // BASE_TASK_SCOPED_SET_TASK_PRIORITY_FOR_CURRENT_THREAD_H_

View File

@@ -0,0 +1,6 @@
monorail: {
component: "Internals>SequenceManager"
}
buganizer_public: {
component_id: 1456159
}

View File

@@ -0,0 +1,6 @@
altimin@chromium.org
carlscab@google.com
etiennep@chromium.org
fdoray@chromium.org
shaseley@chromium.org
skyostil@chromium.org

View File

@@ -0,0 +1,68 @@
# What is this
This file documents high level parts of the sequence manager.
The sequence manager provides a set of prioritized FIFO task queues, which
allows funneling multiple sequences of immediate and delayed tasks on a single
underlying sequence.
## Work Queue and Task selection
Both immediate tasks and delayed tasks are posted to a `TaskQueue` via an
associated `TaskRunner`. `TaskQueue`s use distinct primitive FIFO queues, called
`WorkQueue`s, to manage immediate tasks and delayed tasks. Tasks eventually end
up in their assigned `WorkQueue` which is made directly visible to
`SequenceManager` through `TaskQueueSelector`.
`SequenceManagerImpl::SelectNextTask()` uses
`TaskQueueSelector::SelectWorkQueueToService()` to select the next work queue
based on various policy e.g. priority, from which 1 task is popped at a time.
## Journey of a Task
Task queues have a mechanism to allow efficient cross-thread posting with the
use of 2 work queues, `immediate_incoming_queue` which is used when posting, and
`immediate_work_queue` used to pop tasks from. An immediate task posted from the
main thread is pushed on `immediate_incoming_queue` in
`TaskQueueImpl::PostImmediateTaskImpl()`. If the work queue was empty,
`SequenceManager` is notified and the `TaskQueue` is registered to do
`ReloadEmptyImmediateWorkQueue()` before SequenceManager selects a task, which
moves tasks from `immediate_incoming_queue` to `immediate_work_queue` in batch
for all registered `TaskQueue`s. The tasks then follow the regular work queue
selection mechanism.
## Journey of a WakeUp
A `WakeUp` represents a time at which a delayed task wants to run.
Each `TaskQueueImpl` maintains its own next wake-up as
`main_thread_only().scheduled_wake_up`, associated with the earliest pending
delayed task. It communicates its wake up to the WakeUpQueue via
`WakeUpQueue::SetNextWakeUpForQueue()`. The `WakeUpQueue` is responsible for
determining the single next wake up time for the thread. This is accessed from
`SequenceManagerImpl` and may determine the next run time if there's no
immediate work, which ultimately gets passed to the MessagePump, typically via
`MessagePump::Delegate::NextWorkInfo` (returned by
`ThreadControllerWithMessagePumpImpl::DoWork()`) or by
`MessagePump::ScheduleDelayedWork()` (on rare occasions where the next WakeUp is
scheduled on the main thread from outside a `DoWork()`). When a delayed run time
associated with a wake-up is reached, `WakeUpQueue` is notified through
`WakeUpQueue::MoveReadyDelayedTasksToWorkQueues()` and in turn notifies all
`TaskQueue`s whose wake-up can be resolved. This lets each `TaskQueue`s process
ripe delayed tasks.
## Journey of a delayed Task
A delayed Task posted cross-thread generates an immediate Task to run
`TaskQueueImpl::ScheduleDelayedWorkTask()` which eventually calls
`TaskQueueImpl::PushOntoDelayedIncomingQueueFromMainThread()`, so that it can be
enqueued on the main thread. A delayed Task posted from the main thread skips
this step and calls
`TaskQueueImpl::PushOntoDelayedIncomingQueueFromMainThread()` directly. The Task
is then pushed on `main_thread_only().delayed_incoming_queue` and possibly
updates the next task queue wake-up. Once the delayed run time is reached,
possibly because the wake-up is resolved, the delayed task is moved to
`main_thread_only().delayed_work_queue` and follows the regular work queue
selection mechanism.
## TimeDomain and TickClock
`SequenceManager` and related classes use a common `TickClock` that can be
injected by specifying a `TimeDomain`. A `TimeDomain` is a specialisation of
`TickClock` that gets notified when the `MessagePump` is about to go idle via
TimeDomain::MaybeFastForwardToWakeUp(), and can use the signal to fast forward
in time. This is used in `TaskEnvironment` to support `MOCK_TIME`, and in
devtools to support virtual time.

View File

@@ -0,0 +1,89 @@
// Copyright 2018 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/associated_thread_id.h"
#include "base/check.h"
#include "base/dcheck_is_on.h"
namespace base {
namespace sequence_manager {
namespace internal {
AssociatedThreadId::AssociatedThreadId() = default;
AssociatedThreadId::~AssociatedThreadId() = default;
void AssociatedThreadId::BindToCurrentThread() {
#if DCHECK_IS_ON()
const auto prev_thread_ref =
bound_thread_ref_.load(std::memory_order_relaxed);
DCHECK(prev_thread_ref.is_null() ||
prev_thread_ref == PlatformThread::CurrentRef());
#endif
sequence_token_ = base::internal::SequenceToken::GetForCurrentThread();
bound_thread_ref_.store(PlatformThread::CurrentRef(),
std::memory_order_release);
// Rebind the thread and sequence checkers to the current thread/sequence.
DETACH_FROM_THREAD(thread_checker);
DCHECK_CALLED_ON_VALID_THREAD(thread_checker);
DETACH_FROM_SEQUENCE(sequence_checker);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker);
}
bool AssociatedThreadId::IsBoundToCurrentThread() const {
const PlatformThreadRef bound_thread_ref =
bound_thread_ref_.load(std::memory_order_relaxed);
const PlatformThreadRef in_sequence_thread_ref =
in_sequence_thread_ref_.load(std::memory_order_relaxed);
const PlatformThreadRef current_thread_ref = PlatformThread::CurrentRef();
if (!in_sequence_thread_ref.is_null()) {
// The main thread cannot run when another thread is running "in sequence"
// with it.
CHECK_NE(current_thread_ref, bound_thread_ref);
}
return bound_thread_ref == current_thread_ref;
}
void AssociatedThreadId::AssertInSequenceWithCurrentThread() const {
const PlatformThreadRef in_sequence_thread_ref =
in_sequence_thread_ref_.load(std::memory_order_relaxed);
if (!in_sequence_thread_ref.is_null()) {
CHECK_EQ(in_sequence_thread_ref, PlatformThread::CurrentRef());
return;
}
#if DCHECK_IS_ON()
const PlatformThreadRef bound_thread_ref =
bound_thread_ref_.load(std::memory_order_relaxed);
if (!bound_thread_ref.is_null()) {
CHECK_EQ(bound_thread_ref, PlatformThread::CurrentRef());
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker);
#endif // DCHECK_IS_ON()
}
void AssociatedThreadId::StartInSequenceWithCurrentThread() {
PlatformThreadRef expected = PlatformThreadRef();
bool succeeded = in_sequence_thread_ref_.compare_exchange_strong(
expected, PlatformThread::CurrentRef(), std::memory_order_relaxed);
CHECK(succeeded);
}
void AssociatedThreadId::StopInSequenceWithCurrentThread() {
PlatformThreadRef expected = PlatformThread::CurrentRef();
bool succeeded = in_sequence_thread_ref_.compare_exchange_strong(
expected, PlatformThreadRef(), std::memory_order_relaxed);
CHECK(succeeded);
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,111 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_ASSOCIATED_THREAD_ID_H_
#define BASE_TASK_SEQUENCE_MANAGER_ASSOCIATED_THREAD_ID_H_
#include <atomic>
#include <memory>
#include <optional>
#include "base/base_export.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/sequence_token.h"
#include "base/threading/platform_thread.h"
#include "base/threading/platform_thread_ref.h"
#include "base/threading/thread_checker.h"
namespace base {
namespace sequence_manager {
namespace internal {
// TODO(eseckler): Make this owned by SequenceManager once the TaskQueue
// refactor has happened (https://crbug.com/865411).
//
// This class is thread-safe. But see notes about memory ordering guarantees for
// the various methods.
class BASE_EXPORT AssociatedThreadId
: public base::RefCountedThreadSafe<AssociatedThreadId> {
public:
AssociatedThreadId();
// TODO(eseckler): Replace thread_checker with sequence_checker everywhere.
THREAD_CHECKER(thread_checker);
SEQUENCE_CHECKER(sequence_checker);
static scoped_refptr<AssociatedThreadId> CreateUnbound() {
return MakeRefCounted<AssociatedThreadId>();
}
static scoped_refptr<AssociatedThreadId> CreateBound() {
auto associated_thread = MakeRefCounted<AssociatedThreadId>();
associated_thread->BindToCurrentThread();
return associated_thread;
}
// Rebind the associated thread to the current thread. This allows creating
// the SequenceManager and TaskQueues on a different thread/sequence than the
// one it will manage.
//
// Can only be called once.
void BindToCurrentThread();
// Checks whether this object has already been bound to a thread.
//
// This method guarantees a happens-before ordering with
// BindToCurrentThread(), that is all memory writes that happened-before the
// call to BindToCurrentThread() will become visible side-effects in the
// current thread.
//
// By the time this returns false, the thread may have racily be bound.
// However, a bound thread is never unbound.
bool IsBound() const {
return !bound_thread_ref_.load(std::memory_order_acquire).is_null();
}
// Returns whether this object is bound to the current thread. Returns false
// if this object is not bound to any thread.
//
// Note that this method provides no memory ordering guarantees but those are
// not really needed. If this method returns true we are on the same thread
// that called BindToCurrentThread(). If the method returns false this object
// could be unbound, so there is no possible ordering.
//
// Attention:: The result might be stale by the time this method returns.
bool IsBoundToCurrentThread() const;
// Asserts that the current thread runs in sequence with the thread to which
// this object is bound.
void AssertInSequenceWithCurrentThread() const;
// Returns the `SequenceToken` associated with the bound thread. The caller
// must ensure that this is sequenced after `BindToCurrentThread()`.
base::internal::SequenceToken GetBoundSequenceToken() const {
DCHECK(IsBound());
return sequence_token_;
}
// Indicates that the current thread starts/stops running in sequence with the
// bound thread. `IsBoundToCurrentThread()` and
// `AssertInSequenceWithCurrentThread()` fail if invoked on the bound thread
// when another thread runs in sequence with it (that indicates that mutual
// exclusion is not guaranteed).
void StartInSequenceWithCurrentThread();
void StopInSequenceWithCurrentThread();
private:
friend class base::RefCountedThreadSafe<AssociatedThreadId>;
~AssociatedThreadId();
std::atomic<PlatformThreadRef> bound_thread_ref_;
std::atomic<PlatformThreadRef> in_sequence_thread_ref_;
base::internal::SequenceToken sequence_token_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_ASSOCIATED_THREAD_ID_H_

View File

@@ -0,0 +1,209 @@
// Copyright 2019 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/atomic_flag_set.h"
#include <bit>
#include <utility>
#include "base/check_op.h"
#include "base/functional/callback.h"
namespace base::sequence_manager::internal {
AtomicFlagSet::AtomicFlagSet(
scoped_refptr<const AssociatedThreadId> associated_thread)
: associated_thread_(std::move(associated_thread)) {}
AtomicFlagSet::~AtomicFlagSet() {
DCHECK(!alloc_list_head_);
DCHECK(!partially_free_list_head_);
}
AtomicFlagSet::AtomicFlag::AtomicFlag() = default;
AtomicFlagSet::AtomicFlag::~AtomicFlag() {
ReleaseAtomicFlag();
}
AtomicFlagSet::AtomicFlag::AtomicFlag(AtomicFlagSet* outer,
Group* element,
size_t flag_bit)
: outer_(outer), group_(element), flag_bit_(flag_bit) {}
AtomicFlagSet::AtomicFlag::AtomicFlag(AtomicFlag&& other)
: outer_(other.outer_), group_(other.group_), flag_bit_(other.flag_bit_) {
other.outer_ = nullptr;
other.group_ = nullptr;
}
void AtomicFlagSet::AtomicFlag::SetActive(bool active) {
DCHECK(group_);
if (active) {
// Release semantics are required to ensure that all memory accesses made on
// this thread happen-before any others done on the thread running the
// active callbacks.
group_->flags.fetch_or(flag_bit_, std::memory_order_release);
} else {
// No operation is being performed based on the bit *not* being set (i.e.
// state of other memory is irrelevant); hence no memory order is required
// when unsetting it.
group_->flags.fetch_and(~flag_bit_, std::memory_order_relaxed);
}
}
void AtomicFlagSet::AtomicFlag::ReleaseAtomicFlag() {
if (!group_)
return;
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
SetActive(false);
// If |group_| was full then add it on the partially free list.
if (group_->IsFull())
outer_->AddToPartiallyFreeList(group_);
size_t index = Group::IndexOfFirstFlagSet(flag_bit_);
DCHECK(!group_->flag_callbacks[index].is_null());
group_->flag_callbacks[index] = RepeatingClosure();
group_->allocated_flags &= ~flag_bit_;
// If |group_| has become empty delete it.
if (group_->IsEmpty()) {
auto ptr = group_.ExtractAsDangling();
outer_->RemoveFromPartiallyFreeList(ptr);
outer_->RemoveFromAllocList(ptr);
}
outer_ = nullptr;
group_ = nullptr;
}
AtomicFlagSet::AtomicFlag AtomicFlagSet::AddFlag(RepeatingClosure callback) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
// Allocate a new Group if needed.
if (!partially_free_list_head_) {
AddToAllocList(std::make_unique<Group>());
AddToPartiallyFreeList(alloc_list_head_.get());
}
DCHECK(partially_free_list_head_);
Group* group = partially_free_list_head_;
size_t first_unoccupied_index = group->FindFirstUnallocatedFlag();
DCHECK(!group->flag_callbacks[first_unoccupied_index]);
group->flag_callbacks[first_unoccupied_index] = std::move(callback);
size_t flag_bit = size_t{1} << first_unoccupied_index;
group->allocated_flags |= flag_bit;
if (group->IsFull())
RemoveFromPartiallyFreeList(group);
return AtomicFlag(this, group, flag_bit);
}
void AtomicFlagSet::RunActiveCallbacks() const {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
for (Group* iter = alloc_list_head_.get(); iter; iter = iter->next.get()) {
// Acquire semantics are required to guarantee that all memory side-effects
// made by other threads that were allowed to perform operations are
// synchronized with this thread before it returns from this method.
size_t active_flags = std::atomic_exchange_explicit(
&iter->flags, size_t{0}, std::memory_order_acquire);
// This is O(number of bits set).
while (active_flags) {
size_t index = Group::IndexOfFirstFlagSet(active_flags);
// Clear the flag.
active_flags ^= size_t{1} << index;
iter->flag_callbacks[index].Run();
}
}
}
AtomicFlagSet::Group::Group() = default;
AtomicFlagSet::Group::~Group() {
DCHECK_EQ(allocated_flags, 0u);
DCHECK(!partially_free_list_prev);
DCHECK(!partially_free_list_next);
}
bool AtomicFlagSet::Group::IsFull() const {
return (~allocated_flags) == 0u;
}
bool AtomicFlagSet::Group::IsEmpty() const {
return allocated_flags == 0u;
}
size_t AtomicFlagSet::Group::FindFirstUnallocatedFlag() const {
size_t unallocated_flags = ~allocated_flags;
DCHECK_NE(unallocated_flags, 0u);
size_t index = IndexOfFirstFlagSet(unallocated_flags);
DCHECK_LT(index, static_cast<size_t>(kNumFlags));
return index;
}
// static
size_t AtomicFlagSet::Group::IndexOfFirstFlagSet(size_t flag) {
DCHECK_NE(flag, 0u);
// std::countr_zero is non-negative.
return static_cast<size_t>(std::countr_zero(flag));
}
void AtomicFlagSet::AddToAllocList(std::unique_ptr<Group> group) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
if (alloc_list_head_)
alloc_list_head_->prev = group.get();
group->next = std::move(alloc_list_head_);
alloc_list_head_ = std::move(group);
}
void AtomicFlagSet::RemoveFromAllocList(Group* group) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
if (group->next)
group->next->prev = group->prev;
if (group->prev) {
group->prev->next = std::move(group->next);
} else {
alloc_list_head_ = std::move(group->next);
}
}
void AtomicFlagSet::AddToPartiallyFreeList(Group* group) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK_NE(partially_free_list_head_, group);
DCHECK(!group->partially_free_list_prev);
DCHECK(!group->partially_free_list_next);
if (partially_free_list_head_)
partially_free_list_head_->partially_free_list_prev = group;
group->partially_free_list_next = partially_free_list_head_;
partially_free_list_head_ = group;
}
void AtomicFlagSet::RemoveFromPartiallyFreeList(Group* group) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK(partially_free_list_head_);
// Check |group| is in the list.
DCHECK(partially_free_list_head_ == group || group->partially_free_list_prev);
if (group->partially_free_list_next) {
group->partially_free_list_next->partially_free_list_prev =
group->partially_free_list_prev;
}
if (group->partially_free_list_prev) {
group->partially_free_list_prev->partially_free_list_next =
group->partially_free_list_next;
} else {
partially_free_list_head_ = group->partially_free_list_next;
}
group->partially_free_list_prev = nullptr;
group->partially_free_list_next = nullptr;
}
} // namespace base::sequence_manager::internal

View File

@@ -0,0 +1,139 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_ATOMIC_FLAG_SET_H_
#define BASE_TASK_SEQUENCE_MANAGER_ATOMIC_FLAG_SET_H_
#include <array>
#include <atomic>
#include <memory>
#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/task/sequence_manager/associated_thread_id.h"
namespace base::sequence_manager::internal {
// This class maintains a set of AtomicFlags which can be activated or
// deactivated at any time by any thread. When a flag is created a callback is
// specified and the RunActiveCallbacks method can be invoked to fire callbacks
// for all active flags. Creating releasing or destroying an AtomicFlag must be
// done on the associated thread, as must calling RunActiveCallbacks. This
// class is thread-affine.
class BASE_EXPORT AtomicFlagSet {
protected:
struct Group;
public:
explicit AtomicFlagSet(
scoped_refptr<const AssociatedThreadId> associated_thread);
AtomicFlagSet(const AtomicFlagSet&) = delete;
AtomicFlagSet& operator=(const AtomicFlagSet&) = delete;
// AtomicFlags need to be released (or deleted) before this can be deleted.
~AtomicFlagSet();
// This class is thread-affine in addition SetActive can be called
// concurrently from any thread.
class BASE_EXPORT AtomicFlag {
public:
AtomicFlag();
// Automatically releases the AtomicFlag.
~AtomicFlag();
AtomicFlag(const AtomicFlag&) = delete;
AtomicFlag(AtomicFlag&& other);
// Can be called on any thread. Marks whether the flag is active or not,
// which controls whether RunActiveCallbacks() will fire the associated
// callback or not. In the absence of external synchronization, the value
// set by this call might not immediately be visible to a thread calling
// RunActiveCallbacks(); the only guarantee is that a value set by this will
// eventually be visible to other threads due to cache coherency. Release /
// acquire semantics are used on the underlying atomic operations so if
// RunActiveCallbacks sees the value set by a call to SetActive(), it will
// also see the memory changes that happened prior to that SetActive() call.
void SetActive(bool active);
// Releases the flag. Must be called on the associated thread. SetActive
// can't be called after this.
void ReleaseAtomicFlag();
private:
friend AtomicFlagSet;
AtomicFlag(AtomicFlagSet* outer, Group* element, size_t flag_bit);
raw_ptr<AtomicFlagSet, DanglingUntriaged> outer_ = nullptr;
raw_ptr<Group> group_ = nullptr; // Null when AtomicFlag is invalid.
size_t flag_bit_ = 0; // This is 1 << index of this flag within the group.
};
// Adds a new flag to the set. The |callback| will be fired by
// RunActiveCallbacks if the flag is active. Must be called on the associated
// thread.
AtomicFlag AddFlag(RepeatingClosure callback);
// Runs the registered callback for all flags marked as active and atomically
// resets all flags to inactive. Must be called on the associated thread.
void RunActiveCallbacks() const;
protected:
Group* GetAllocListForTesting() const { return alloc_list_head_.get(); }
Group* GetPartiallyFreeListForTesting() const {
return partially_free_list_head_;
}
// Wraps a single std::atomic<size_t> which is shared by a number of
// AtomicFlag's with one bit per flag.
struct BASE_EXPORT Group {
Group();
Group(const Group&) = delete;
Group& operator=(const Group&) = delete;
~Group();
static constexpr int kNumFlags = sizeof(size_t) * 8;
std::atomic<size_t> flags = {0};
size_t allocated_flags = 0;
std::array<RepeatingClosure, kNumFlags> flag_callbacks;
raw_ptr<Group> prev = nullptr;
std::unique_ptr<Group> next;
raw_ptr<Group> partially_free_list_prev = nullptr;
raw_ptr<Group> partially_free_list_next = nullptr;
bool IsFull() const;
bool IsEmpty() const;
// Returns the index of the first unallocated flag. Must not be called when
// all flags are set.
size_t FindFirstUnallocatedFlag() const;
// Computes the index of the |flag_callbacks| based on the number of leading
// zero bits in |flag|.
static size_t IndexOfFirstFlagSet(size_t flag);
};
private:
void AddToAllocList(std::unique_ptr<Group> element);
// This deletes |element|.
void RemoveFromAllocList(Group* element);
void AddToPartiallyFreeList(Group* element);
// This does not delete |element|.
void RemoveFromPartiallyFreeList(Group* element);
const scoped_refptr<const AssociatedThreadId> associated_thread_;
std::unique_ptr<Group> alloc_list_head_;
raw_ptr<Group> partially_free_list_head_ = nullptr;
};
} // namespace base::sequence_manager::internal
#endif // BASE_TASK_SEQUENCE_MANAGER_ATOMIC_FLAG_SET_H_

View File

@@ -0,0 +1,69 @@
// Copyright 2021 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/delayed_task_handle_delegate.h"
#include "base/task/sequence_manager/task_queue_impl.h"
namespace base {
namespace sequence_manager {
namespace internal {
DelayedTaskHandleDelegate::DelayedTaskHandleDelegate(TaskQueueImpl* outer)
: outer_(outer) {}
DelayedTaskHandleDelegate::~DelayedTaskHandleDelegate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!IsValid());
}
WeakPtr<DelayedTaskHandleDelegate> DelayedTaskHandleDelegate::AsWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_ptr_factory_.GetWeakPtr();
}
bool DelayedTaskHandleDelegate::IsValid() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_ptr_factory_.HasWeakPtrs();
}
void DelayedTaskHandleDelegate::CancelTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsValid())
return;
weak_ptr_factory_.InvalidateWeakPtrs();
// If the task is still inside the heap, then it can be removed directly.
if (heap_handle_.IsValid())
outer_->RemoveCancelableTask(heap_handle_);
}
void DelayedTaskHandleDelegate::SetHeapHandle(HeapHandle heap_handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(heap_handle.IsValid());
heap_handle_ = heap_handle;
}
void DelayedTaskHandleDelegate::ClearHeapHandle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
heap_handle_ = HeapHandle();
}
HeapHandle DelayedTaskHandleDelegate::GetHeapHandle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return heap_handle_;
}
void DelayedTaskHandleDelegate::WillRunTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
// The task must be removed from the heap before running it.
DCHECK(!heap_handle_.IsValid());
weak_ptr_factory_.InvalidateWeakPtrs();
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,65 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_DELAYED_TASK_HANDLE_DELEGATE_H_
#define BASE_TASK_SEQUENCE_MANAGER_DELAYED_TASK_HANDLE_DELEGATE_H_
#include "base/containers/intrusive_heap.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/delayed_task_handle.h"
namespace base {
namespace sequence_manager {
namespace internal {
class TaskQueueImpl;
class DelayedTaskHandleDelegate : public DelayedTaskHandle::Delegate {
public:
explicit DelayedTaskHandleDelegate(TaskQueueImpl* outer);
DelayedTaskHandleDelegate(const DelayedTaskHandleDelegate&) = delete;
DelayedTaskHandleDelegate& operator=(const DelayedTaskHandleDelegate&) =
delete;
~DelayedTaskHandleDelegate() override;
WeakPtr<DelayedTaskHandleDelegate> AsWeakPtr();
// DelayedTaskHandle::Delegate:
bool IsValid() const override;
void CancelTask() override;
void SetHeapHandle(HeapHandle heap_handle);
void ClearHeapHandle();
HeapHandle GetHeapHandle();
// Indicates that this task will be executed. This will invalidate the handle.
void WillRunTask();
private:
// The TaskQueueImpl where the task was posted.
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of speedometer3).
RAW_PTR_EXCLUSION TaskQueueImpl* const outer_
GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;
// The HeapHandle to the task, if the task is in the DelayedIncomingQueue,
// invalid otherwise.
HeapHandle heap_handle_ GUARDED_BY_CONTEXT(sequence_checker_);
SEQUENCE_CHECKER(sequence_checker_);
// Allows TaskQueueImpl to retain a weak reference to |this|. An outstanding
// weak pointer indicates that the task is valid.
WeakPtrFactory<DelayedTaskHandleDelegate> weak_ptr_factory_
GUARDED_BY_CONTEXT(sequence_checker_){this};
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_DELAYED_TASK_HANDLE_DELEGATE_H_

View File

@@ -0,0 +1,61 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_H_
#define BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_H_
#include <stdint.h>
#include <limits>
namespace base {
namespace sequence_manager {
namespace internal {
class EnqueueOrderGenerator;
}
// 64-bit number which is used to order tasks.
// SequenceManager assumes this number will never overflow.
class EnqueueOrder {
public:
EnqueueOrder() : value_(kNone) {}
~EnqueueOrder() = default;
static EnqueueOrder none() { return EnqueueOrder(kNone); }
static EnqueueOrder blocking_fence() { return EnqueueOrder(kBlockingFence); }
// Returns an EnqueueOrder that compares greater than any other EnqueueOrder.
static EnqueueOrder max() {
return EnqueueOrder(std::numeric_limits<uint64_t>::max());
}
// It's okay to use EnqueueOrder in boolean expressions keeping in mind
// that some non-zero values have a special meaning.
operator uint64_t() const { return value_; }
static EnqueueOrder FromIntForTesting(uint64_t value) {
return EnqueueOrder(value);
}
private:
// EnqueueOrderGenerator is the only class allowed to create an EnqueueOrder
// with a non-default constructor.
friend class internal::EnqueueOrderGenerator;
explicit EnqueueOrder(uint64_t value) : value_(value) {}
enum SpecialValues : uint64_t {
kNone = 0,
kBlockingFence = 1,
kFirst = 2,
};
uint64_t value_;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_H_

View File

@@ -0,0 +1,18 @@
// Copyright 2018 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/enqueue_order_generator.h"
namespace base {
namespace sequence_manager {
namespace internal {
EnqueueOrderGenerator::EnqueueOrderGenerator()
: counter_(EnqueueOrder::kFirst) {}
EnqueueOrderGenerator::~EnqueueOrderGenerator() = default;
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,42 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_GENERATOR_H_
#define BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_GENERATOR_H_
#include <stdint.h>
#include <atomic>
#include "base/base_export.h"
#include "base/task/sequence_manager/enqueue_order.h"
namespace base {
namespace sequence_manager {
namespace internal {
// EnqueueOrder can't be created from a raw number in non-test code.
// EnqueueOrderGenerator is used to create it with strictly monotonic guarantee.
class BASE_EXPORT EnqueueOrderGenerator {
public:
EnqueueOrderGenerator();
EnqueueOrderGenerator(const EnqueueOrderGenerator&) = delete;
EnqueueOrderGenerator& operator=(const EnqueueOrderGenerator&) = delete;
~EnqueueOrderGenerator();
// Can be called from any thread.
EnqueueOrder GenerateNext() {
return EnqueueOrder(std::atomic_fetch_add_explicit(
&counter_, uint64_t(1), std::memory_order_relaxed));
}
private:
std::atomic<uint64_t> counter_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_GENERATOR_H_

View File

@@ -0,0 +1,45 @@
// Copyright 2021 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/fence.h"
#include "base/check.h"
#include "base/json/values_util.h"
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/task/sequence_manager/task_order.h"
#include "base/time/time.h"
#include "base/values.h"
namespace base {
namespace sequence_manager {
namespace internal {
Fence::Fence(const TaskOrder& task_order) : task_order_(task_order) {
DCHECK_NE(task_order_.enqueue_order(), EnqueueOrder::none());
}
Fence::Fence(EnqueueOrder enqueue_order,
TimeTicks delayed_run_time,
int sequence_num)
: task_order_(enqueue_order, delayed_run_time, sequence_num) {}
Fence::Fence(const Fence& other) = default;
Fence& Fence::operator=(const Fence& other) = default;
Fence::~Fence() = default;
// static
Fence Fence::BlockingFence() {
return CreateWithEnqueueOrder(EnqueueOrder::blocking_fence());
}
// static
Fence Fence::CreateWithEnqueueOrder(EnqueueOrder enqueue_order) {
return Fence(enqueue_order, TimeTicks(), 0);
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,67 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_FENCE_H_
#define BASE_TASK_SEQUENCE_MANAGER_FENCE_H_
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/task/sequence_manager/task_order.h"
namespace base {
class TimeTicks;
namespace sequence_manager {
namespace internal {
class TaskQueueImpl;
// `Fence`s are used to prevent the execution of tasks starting with a
// particular `TaskOrder`, such that for a `Task` and a `Fence`, if
// task.task_order() >= fence.task_order(), then the task is blocked from
// running. Blocking fences are a special kind of fence that have a `TaskOrder`
// less than that of any `Task`.
class BASE_EXPORT Fence {
public:
// Creates a `Fence` with the same `TaskOrder` as `task_order`, which is
// useful for creating a fence relative to a particular `Task`.
// `task_order.enqueue_order()` must be "set", i.e. it cannot be
// `EnqueueOrder::none()`.
explicit Fence(const TaskOrder& task_order);
Fence(const Fence& other);
Fence& operator=(const Fence& other);
~Fence();
// Creates a blocking fence which has a `TaskOrder` that is less than that of
// all tasks.
static Fence BlockingFence();
const TaskOrder& task_order() const LIFETIME_BOUND { return task_order_; }
// Returns true iff this is a blocking fence.
bool IsBlockingFence() const {
return task_order_.enqueue_order() == EnqueueOrder::blocking_fence();
}
private:
friend class TaskQueueImpl; // For `CreateWithEnqueueOrder()`.
Fence(EnqueueOrder enqueue_order,
TimeTicks delayed_run_time,
int sequence_num);
// Creates a `Fence` with `enqueue_order` and a null delayed run time.
// `enqueue_order` cannot be EnqueueOrder::none().
static Fence CreateWithEnqueueOrder(EnqueueOrder enqueue_order);
TaskOrder task_order_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_FENCE_H_

View File

@@ -0,0 +1,389 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_LAZILY_DEALLOCATED_DEQUE_H_
#define BASE_TASK_SEQUENCE_MANAGER_LAZILY_DEALLOCATED_DEQUE_H_
#include <algorithm>
#include <cmath>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/debug/alias.h"
#include "base/gtest_prod_util.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/raw_span.h"
#include "base/time/time.h"
namespace base {
namespace sequence_manager {
namespace internal {
// A LazilyDeallocatedDeque specialized for the SequenceManager's usage
// patterns. The queue generally grows while tasks are added and then removed
// until empty and the cycle repeats.
//
// The main difference between sequence_manager::LazilyDeallocatedDeque and
// others is memory management. For performance (memory allocation isn't free)
// we don't automatically reclaiming memory when the queue becomes empty.
// Instead we rely on the surrounding code periodically calling
// MaybeShrinkQueue, ideally when the queue is empty.
//
// We keep track of the maximum recent queue size and rate limit
// MaybeShrinkQueue to avoid unnecessary churn.
//
// NB this queue isn't by itself thread safe.
template <typename T, TimeTicks (*now_source)() = TimeTicks::Now>
class LazilyDeallocatedDeque {
public:
enum {
// Minimum allocation for a ring. Note a ring of size 4 will only hold up to
// 3 elements.
kMinimumRingSize = 4,
// Maximum "wasted" capacity allowed when considering if we should resize
// the backing store.
kReclaimThreshold = 16,
// Used to rate limit how frequently MaybeShrinkQueue actually shrinks the
// queue.
kMinimumShrinkIntervalInSeconds = 5
};
LazilyDeallocatedDeque() = default;
LazilyDeallocatedDeque(const LazilyDeallocatedDeque&) = delete;
LazilyDeallocatedDeque& operator=(const LazilyDeallocatedDeque&) = delete;
~LazilyDeallocatedDeque() { clear(); }
bool empty() const { return size_ == 0; }
size_t max_size() const { return max_size_; }
size_t size() const { return size_; }
size_t capacity() const {
size_t capacity = 0;
for (const Ring* iter = head_.get(); iter; iter = iter->next_.get()) {
capacity += iter->capacity();
}
return capacity;
}
void clear() {
while (head_) {
head_ = std::move(head_->next_);
}
tail_ = nullptr;
size_ = 0;
}
// Assumed to be an uncommon operation.
void push_front(T t) {
if (!head_) {
DCHECK(!tail_);
head_ = std::make_unique<Ring>(kMinimumRingSize);
tail_ = head_.get();
}
// Grow if needed, by the minimum amount.
if (!head_->CanPush()) {
// TODO(alexclarke): Remove once we've understood the OOMs.
size_t size = size_;
base::debug::Alias(&size);
std::unique_ptr<Ring> new_ring = std::make_unique<Ring>(kMinimumRingSize);
new_ring->next_ = std::move(head_);
head_ = std::move(new_ring);
}
head_->push_front(std::move(t));
max_size_ = std::max(max_size_, ++size_);
}
// Assumed to be a common operation.
void push_back(T t) {
if (!head_) {
DCHECK(!tail_);
head_ = std::make_unique<Ring>(kMinimumRingSize);
tail_ = head_.get();
}
// Grow if needed.
if (!tail_->CanPush()) {
// TODO(alexclarke): Remove once we've understood the OOMs.
size_t size = size_;
base::debug::Alias(&size);
// Doubling the size is a common strategy, but one which can be wasteful
// so we use a (somewhat) slower growth curve.
tail_->next_ = std::make_unique<Ring>(2 + tail_->capacity() +
(tail_->capacity() / 2));
tail_ = tail_->next_.get();
}
tail_->push_back(std::move(t));
max_size_ = std::max(max_size_, ++size_);
}
T& front() LIFETIME_BOUND {
DCHECK(head_);
return head_->front();
}
const T& front() const LIFETIME_BOUND {
DCHECK(head_);
return head_->front();
}
T& back() LIFETIME_BOUND {
DCHECK(tail_);
return tail_->back();
}
const T& back() const LIFETIME_BOUND {
DCHECK(tail_);
return tail_->back();
}
void pop_front() {
DCHECK(head_);
DCHECK(!head_->empty());
DCHECK(tail_);
DCHECK_GT(size_, 0u);
head_->pop_front();
// If the ring has become empty and we have several rings then, remove the
// head one (which we expect to have lower capacity than the remaining
// ones).
if (head_->empty() && head_->next_) {
head_ = std::move(head_->next_);
}
--size_;
}
void swap(LazilyDeallocatedDeque& other) {
std::swap(head_, other.head_);
std::swap(tail_, other.tail_);
std::swap(size_, other.size_);
std::swap(max_size_, other.max_size_);
std::swap(next_resize_time_, other.next_resize_time_);
}
void MaybeShrinkQueue() {
if (!tail_)
return;
DCHECK_GE(max_size_, size_);
// Rate limit how often we shrink the queue because it's somewhat expensive.
TimeTicks current_time = now_source();
if (current_time < next_resize_time_)
return;
// Due to the way the Ring works we need 1 more slot than is used.
size_t new_capacity = max_size_ + 1;
if (new_capacity < kMinimumRingSize)
new_capacity = kMinimumRingSize;
// Reset |max_size_| so that unless usage has spiked up we will consider
// reclaiming it next time.
max_size_ = size_;
// Only realloc if the current capacity is sufficiently greater than the
// observed maximum size for the previous period.
if (new_capacity + kReclaimThreshold >= capacity())
return;
SetCapacity(new_capacity);
next_resize_time_ = current_time + Seconds(kMinimumShrinkIntervalInSeconds);
}
void SetCapacity(size_t new_capacity) {
std::unique_ptr<Ring> new_ring = std::make_unique<Ring>(new_capacity);
DCHECK_GE(new_capacity, size_ + 1);
// Preserve the |size_| which counts down to zero in the while loop.
size_t real_size = size_;
while (!empty()) {
DCHECK(new_ring->CanPush());
new_ring->push_back(std::move(head_->front()));
pop_front();
}
size_ = real_size;
DCHECK_EQ(head_.get(), tail_);
head_ = std::move(new_ring);
tail_ = head_.get();
}
private:
FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingPushFront);
FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingPushBack);
FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingCanPush);
FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingPushPopPushPop);
struct Ring {
explicit Ring(size_t capacity) {
DCHECK_GE(capacity, kMinimumRingSize);
std::tie(backing_store_, data_) = AlignedUninitCharArray<T>(capacity);
}
Ring(const Ring&) = delete;
Ring& operator=(const Ring&) = delete;
~Ring() {
while (!empty()) {
pop_front();
}
}
bool empty() const { return back_index_ == before_front_index_; }
size_t capacity() const { return data_.size(); }
bool CanPush() const {
return before_front_index_ != CircularIncrement(back_index_);
}
void push_front(T&& t) {
// Mustn't appear to become empty.
CHECK_NE(CircularDecrement(before_front_index_), back_index_);
std::construct_at(data_.get_at(before_front_index_), std::move(t));
before_front_index_ = CircularDecrement(before_front_index_);
}
void push_back(T&& t) {
back_index_ = CircularIncrement(back_index_);
CHECK(!empty()); // Mustn't appear to become empty.
std::construct_at(data_.get_at(back_index_), std::move(t));
}
void pop_front() {
CHECK(!empty());
before_front_index_ = CircularIncrement(before_front_index_);
data_[before_front_index_].~T();
}
T& front() LIFETIME_BOUND {
CHECK(!empty());
return data_[CircularIncrement(before_front_index_)];
}
const T& front() const LIFETIME_BOUND {
CHECK(!empty());
return data_[CircularIncrement(before_front_index_)];
}
T& back() LIFETIME_BOUND {
CHECK(!empty());
return data_[back_index_];
}
const T& back() const LIFETIME_BOUND {
CHECK(!empty());
return data_[back_index_];
}
size_t CircularDecrement(size_t index) const {
if (index == 0)
return capacity() - 1;
return index - 1;
}
size_t CircularIncrement(size_t index) const {
CHECK_LT(index, capacity());
++index;
if (index == capacity()) {
return 0;
}
return index;
}
AlignedHeapArray<char> backing_store_;
raw_span<T> data_;
// Indices into `data_` for one-before-the-first element and the last
// element. The back_index_ may be less than before_front_index_ if the
// elements wrap around the back of the array. If they are equal, then the
// Ring is empty.
size_t before_front_index_ = 0;
size_t back_index_ = 0;
std::unique_ptr<Ring> next_ = nullptr;
};
public:
class Iterator {
public:
using value_type = T;
using pointer = const T*;
using reference = const T&;
const T& operator->() const { return ring_->data_[index_]; }
const T& operator*() const { return ring_->data_[index_]; }
Iterator& operator++() {
if (index_ == ring_->back_index_) {
ring_ = ring_->next_.get();
index_ =
ring_ ? ring_->CircularIncrement(ring_->before_front_index_) : 0;
} else {
index_ = ring_->CircularIncrement(index_);
}
return *this;
}
operator bool() const { return !!ring_; }
private:
explicit Iterator(const Ring* ring) {
if (!ring || ring->empty()) {
ring_ = nullptr;
index_ = 0;
return;
}
ring_ = ring;
index_ = ring_->CircularIncrement(ring->before_front_index_);
}
raw_ptr<const Ring> ring_;
size_t index_;
friend class LazilyDeallocatedDeque;
};
Iterator begin() const { return Iterator(head_.get()); }
Iterator end() const { return Iterator(nullptr); }
private:
// We maintain a list of Ring buffers, to enable us to grow without copying,
// but most of the time we aim to have only one active Ring.
std::unique_ptr<Ring> head_;
// `tail_` is not a raw_ptr<...> for performance reasons (based on analysis of
// sampling profiler data and tab_search:top100:2020).
RAW_PTR_EXCLUSION Ring* tail_ = nullptr;
size_t size_ = 0;
size_t max_size_ = 0;
TimeTicks next_resize_time_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_LAZILY_DEALLOCATED_DEQUE_H_

View File

@@ -0,0 +1,192 @@
// Copyright 2018 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/sequence_manager.h"
#include <utility>
namespace base {
namespace sequence_manager {
namespace {
#if BUILDFLAG(ENABLE_BASE_TRACING)
perfetto::protos::pbzero::SequenceManagerTask::Priority
DefaultTaskPriorityToProto(TaskQueue::QueuePriority priority) {
DCHECK_EQ(priority, static_cast<TaskQueue::QueuePriority>(
TaskQueue::DefaultQueuePriority::kNormalPriority));
return perfetto::protos::pbzero::SequenceManagerTask::Priority::
NORMAL_PRIORITY;
}
#endif
void CheckPriorities(TaskQueue::QueuePriority priority_count,
TaskQueue::QueuePriority default_priority) {
CHECK_LE(static_cast<size_t>(priority_count),
SequenceManager::PrioritySettings::kMaxPriorities)
<< "The number of priorities cannot exceed kMaxPriorities.";
CHECK_LT(static_cast<size_t>(default_priority), priority_count)
<< "The default priority must be within the priority range.";
}
} // namespace
SequenceManager::MetricRecordingSettings::MetricRecordingSettings(
double task_thread_time_sampling_rate)
: task_sampling_rate_for_recording_cpu_time(
base::ThreadTicks::IsSupported() ? task_thread_time_sampling_rate
: 0) {}
// static
SequenceManager::PrioritySettings
SequenceManager::PrioritySettings::CreateDefault() {
PrioritySettings settings(
TaskQueue::DefaultQueuePriority::kQueuePriorityCount,
TaskQueue::DefaultQueuePriority::kNormalPriority);
#if BUILDFLAG(ENABLE_BASE_TRACING)
settings.SetProtoPriorityConverter(&DefaultTaskPriorityToProto);
#endif
return settings;
}
SequenceManager::PrioritySettings::PrioritySettings(
TaskQueue::QueuePriority priority_count,
TaskQueue::QueuePriority default_priority)
#if DCHECK_IS_ON()
: PrioritySettings(priority_count,
default_priority,
std::vector<TimeDelta>(priority_count),
std::vector<TimeDelta>(priority_count)){}
#else
: priority_count_(priority_count), default_priority_(default_priority) {
CheckPriorities(priority_count, default_priority);
}
#endif
#if DCHECK_IS_ON()
SequenceManager::PrioritySettings::PrioritySettings(
TaskQueue::QueuePriority priority_count,
TaskQueue::QueuePriority default_priority,
std::vector<TimeDelta> per_priority_cross_thread_task_delay,
std::vector<TimeDelta> per_priority_same_thread_task_delay)
: priority_count_(priority_count),
default_priority_(default_priority),
per_priority_cross_thread_task_delay_(
std::move(per_priority_cross_thread_task_delay)),
per_priority_same_thread_task_delay_(
std::move(per_priority_same_thread_task_delay)) {
CheckPriorities(priority_count, default_priority);
DCHECK_EQ(priority_count, per_priority_cross_thread_task_delay_.size());
DCHECK_EQ(priority_count, per_priority_same_thread_task_delay_.size());
}
#endif
#if BUILDFLAG(ENABLE_BASE_TRACING)
perfetto::protos::pbzero::SequenceManagerTask::Priority
SequenceManager::PrioritySettings::TaskPriorityToProto(
TaskQueue::QueuePriority priority) const {
// `proto_priority_converter_` will be null in some unit tests, but those
// tests should not be tracing.
DCHECK(proto_priority_converter_)
<< "A tracing priority-to-proto-priority function was not provided";
return proto_priority_converter_(priority);
}
#endif
SequenceManager::PrioritySettings::~PrioritySettings() = default;
SequenceManager::PrioritySettings::PrioritySettings(
PrioritySettings&&) noexcept = default;
SequenceManager::PrioritySettings& SequenceManager::PrioritySettings::operator=(
PrioritySettings&&) = default;
SequenceManager::Settings::Settings() = default;
SequenceManager::Settings::Settings(Settings&& move_from) noexcept = default;
SequenceManager::Settings::~Settings() = default;
SequenceManager::Settings::Builder::Builder() = default;
SequenceManager::Settings::Builder::~Builder() = default;
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetMessagePumpType(
MessagePumpType message_loop_type_val) {
settings_.message_loop_type = message_loop_type_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetRandomisedSamplingEnabled(
bool randomised_sampling_enabled_val) {
settings_.randomised_sampling_enabled = randomised_sampling_enabled_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetTickClock(const TickClock* clock_val) {
settings_.clock = clock_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetAddQueueTimeToTasks(
bool add_queue_time_to_tasks_val) {
settings_.add_queue_time_to_tasks = add_queue_time_to_tasks_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetCanRunTasksByBatches(
bool can_run_tasks_by_batches_val) {
settings_.can_run_tasks_by_batches = can_run_tasks_by_batches_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetPrioritySettings(
SequenceManager::PrioritySettings settings) {
settings_.priority_settings = std::move(settings);
return *this;
}
#if DCHECK_IS_ON()
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetRandomTaskSelectionSeed(
uint64_t random_task_selection_seed_val) {
settings_.random_task_selection_seed = random_task_selection_seed_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetTaskLogging(
TaskLogging task_execution_logging_val) {
settings_.task_execution_logging = task_execution_logging_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetLogPostTask(bool log_post_task_val) {
settings_.log_post_task = log_post_task_val;
return *this;
}
SequenceManager::Settings::Builder&
SequenceManager::Settings::Builder::SetLogTaskDelayExpiry(
bool log_task_delay_expiry_val) {
settings_.log_task_delay_expiry = log_task_delay_expiry_val;
return *this;
}
#endif // DCHECK_IS_ON()
SequenceManager::Settings SequenceManager::Settings::Builder::Build() {
return std::move(settings_);
}
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,384 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_H_
#define BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_H_
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "base/message_loop/message_pump_type.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/task/sequence_manager/task_time_observer.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_tick_clock.h"
namespace base {
class MessagePump;
class TaskObserver;
namespace sequence_manager {
class TimeDomain;
// SequenceManager manages TaskQueues which have different properties
// (e.g. priority, common task type) multiplexing all posted tasks into
// a single backing sequence (currently bound to a single thread, which is
// refererred as *main thread* in the comments below). SequenceManager
// implementation can be used in a various ways to apply scheduling logic.
class BASE_EXPORT SequenceManager {
public:
class Observer {
public:
virtual ~Observer() = default;
// Called back on the main thread.
virtual void OnBeginNestedRunLoop() = 0;
virtual void OnExitNestedRunLoop() = 0;
};
struct MetricRecordingSettings {
// This parameter will be updated for consistency on creation (setting
// value to 0 when ThreadTicks are not supported).
explicit MetricRecordingSettings(
double task_sampling_rate_for_recording_cpu_time);
// The proportion of the tasks for which the cpu time will be
// sampled or 0 if this is not enabled.
// Since randomised sampling requires the use of Rand(), it is enabled only
// on platforms which support it.
// If it is 1 then cpu time is measured for each task, so the integral
// metrics (as opposed to per-task metrics) can be recorded.
double task_sampling_rate_for_recording_cpu_time = 0;
bool records_cpu_time_for_some_tasks() const {
return task_sampling_rate_for_recording_cpu_time > 0.0;
}
bool records_cpu_time_for_all_tasks() const {
return task_sampling_rate_for_recording_cpu_time == 1.0;
}
};
class BASE_EXPORT PrioritySettings {
public:
// This limit is based on an implementation detail of `TaskQueueSelector`'s
// `ActivePriorityTracker`, which can be refactored if more priorities are
// needed.
static constexpr size_t kMaxPriorities = sizeof(size_t) * 8 - 1;
static PrioritySettings CreateDefault();
template <typename T,
typename = typename std::enable_if_t<std::is_enum_v<T>>>
PrioritySettings(T priority_count, T default_priority)
: PrioritySettings(
static_cast<TaskQueue::QueuePriority>(priority_count),
static_cast<TaskQueue::QueuePriority>(default_priority)) {
static_assert(
std::is_same_v<std::underlying_type_t<T>, TaskQueue::QueuePriority>,
"Enumerated priorites must have the same underlying type as "
"TaskQueue::QueuePriority");
}
PrioritySettings(TaskQueue::QueuePriority priority_count,
TaskQueue::QueuePriority default_priority);
~PrioritySettings();
PrioritySettings(PrioritySettings&&) noexcept;
PrioritySettings& operator=(PrioritySettings&&);
TaskQueue::QueuePriority priority_count() const { return priority_count_; }
TaskQueue::QueuePriority default_priority() const {
return default_priority_;
}
#if BUILDFLAG(ENABLE_BASE_TRACING)
void SetProtoPriorityConverter(
perfetto::protos::pbzero::SequenceManagerTask::Priority (
*proto_priority_converter)(TaskQueue::QueuePriority)) {
proto_priority_converter_ = proto_priority_converter;
}
perfetto::protos::pbzero::SequenceManagerTask::Priority TaskPriorityToProto(
TaskQueue::QueuePriority priority) const;
#endif
private:
TaskQueue::QueuePriority priority_count_;
TaskQueue::QueuePriority default_priority_;
#if BUILDFLAG(ENABLE_BASE_TRACING)
perfetto::protos::pbzero::SequenceManagerTask::Priority (
*proto_priority_converter_)(TaskQueue::QueuePriority) = nullptr;
#endif
#if DCHECK_IS_ON()
public:
PrioritySettings(
TaskQueue::QueuePriority priority_count,
TaskQueue::QueuePriority default_priority,
std::vector<TimeDelta> per_priority_cross_thread_task_delay,
std::vector<TimeDelta> per_priority_same_thread_task_delay);
const std::vector<TimeDelta>& per_priority_cross_thread_task_delay() const
LIFETIME_BOUND {
return per_priority_cross_thread_task_delay_;
}
const std::vector<TimeDelta>& per_priority_same_thread_task_delay() const
LIFETIME_BOUND {
return per_priority_same_thread_task_delay_;
}
private:
// Scheduler policy induced raciness is an area of concern. This lets us
// apply an extra delay per priority for cross thread posting.
std::vector<TimeDelta> per_priority_cross_thread_task_delay_;
// Like the above but for same thread posting.
std::vector<TimeDelta> per_priority_same_thread_task_delay_;
#endif
};
// Settings defining the desired SequenceManager behaviour: the type of the
// MessageLoop and whether randomised sampling should be enabled.
struct BASE_EXPORT Settings {
class Builder;
Settings();
Settings(const Settings&) = delete;
Settings& operator=(const Settings&) = delete;
// In the future MessagePump (which is move-only) will also be a setting,
// so we are making Settings move-only in preparation.
Settings(Settings&& move_from) noexcept;
~Settings();
MessagePumpType message_loop_type = MessagePumpType::DEFAULT;
bool randomised_sampling_enabled = false;
raw_ptr<const TickClock, DanglingUntriaged> clock =
DefaultTickClock::GetInstance();
// Whether or not queueing timestamp will be added to tasks.
bool add_queue_time_to_tasks = false;
// Whether many tasks may run between each check for native work.
bool can_run_tasks_by_batches = false;
PrioritySettings priority_settings = PrioritySettings::CreateDefault();
#if DCHECK_IS_ON()
// TODO(alexclarke): Consider adding command line flags to control these.
enum class TaskLogging {
kNone,
kEnabled,
kEnabledWithBacktrace,
// Logs high priority tasks and the lower priority tasks they skipped
// past. Useful for debugging test failures caused by scheduler policy
// changes.
kReorderedOnly,
};
TaskLogging task_execution_logging = TaskLogging::kNone;
// If true PostTask will emit a debug log.
bool log_post_task = false;
// If true debug logs will be emitted when a delayed task becomes eligible
// to run.
bool log_task_delay_expiry = false;
// If not zero this seeds a PRNG used by the task selection logic to choose
// a random TaskQueue for a given priority rather than the TaskQueue with
// the oldest EnqueueOrder.
uint64_t random_task_selection_seed = 0;
#endif // DCHECK_IS_ON()
};
virtual ~SequenceManager() = default;
// Binds the SequenceManager and its TaskQueues to the current thread. Should
// only be called once. Note that CreateSequenceManagerOnCurrentThread()
// performs this initialization automatically.
virtual void BindToCurrentThread() = 0;
// Returns the task runner the current task was posted on. Returns null if no
// task is currently running. Must be called on the bound thread.
virtual scoped_refptr<SequencedTaskRunner> GetTaskRunnerForCurrentTask() = 0;
// Finishes the initialization for a SequenceManager created via
// CreateUnboundSequenceManager(). Must not be called in any other
// circumstances. The ownership of the pump is transferred to SequenceManager.
virtual void BindToMessagePump(std::unique_ptr<MessagePump> message_pump) = 0;
// Must be called on the main thread.
// Can be called only once, before creating TaskQueues.
// Observer must outlive the SequenceManager.
virtual void SetObserver(Observer* observer) = 0;
// Must be called on the main thread.
virtual void AddTaskTimeObserver(TaskTimeObserver* task_time_observer) = 0;
virtual void RemoveTaskTimeObserver(TaskTimeObserver* task_time_observer) = 0;
// Sets `time_domain` to be used by this scheduler and associated task queues.
// Only one time domain can be set at a time. `time_domain` must outlive this
// SequenceManager, even if ResetTimeDomain() is called. This has no effect on
// previously scheduled tasks and it is recommended that `time_domain` be set
// before posting any task to avoid inconsistencies in time. Otherwise,
// replacing `time_domain` is very subtle and should be reserved for developer
// only use cases (e.g. virtual time in devtools) where any flakiness caused
// by a racy time update isn't surprising.
virtual void SetTimeDomain(TimeDomain* time_domain) = 0;
// Disassociates the current `time_domain` and reverts to using
// RealTimeDomain.
virtual void ResetTimeDomain() = 0;
virtual const TickClock* GetTickClock() const = 0;
virtual TimeTicks NowTicks() const = 0;
// Returns a wake-up for the next delayed task which is not ripe for
// execution. If there are no such tasks (immediate tasks don't count),
// returns nullopt.
virtual std::optional<WakeUp> GetNextDelayedWakeUp() const = 0;
// Sets the SingleThreadTaskRunner that will be returned by
// SingleThreadTaskRunner::GetCurrentDefault on the main thread.
virtual void SetDefaultTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) = 0;
// Removes all canceled delayed tasks, and considers resizing to fit all
// internal queues.
virtual void ReclaimMemory() = 0;
// Returns true if no tasks were executed in TaskQueues that monitor
// quiescence since the last call to this method.
virtual bool GetAndClearSystemIsQuiescentBit() = 0;
// Set the number of tasks executed in a single SequenceManager invocation.
// Increasing this number reduces the overhead of the tasks dispatching
// logic at the cost of a potentially worse latency. 1 by default.
virtual void SetWorkBatchSize(int work_batch_size) = 0;
// Enables crash keys that can be set in the scope of a task which help
// to identify the culprit if upcoming work results in a crash.
// Key names must be thread-specific to avoid races and corrupted crash dumps.
virtual void EnableCrashKeys(const char* async_stack_crash_key) = 0;
// Returns the metric recording configuration for the current SequenceManager.
virtual const MetricRecordingSettings& GetMetricRecordingSettings() const = 0;
virtual TaskQueue::QueuePriority GetPriorityCount() const = 0;
// Creates a `TaskQueue` and returns a `TaskQueue::Handle`for it. The queue is
// owned by the handle and shut down when the handle is destroyed. Must be
// called on the main thread.
virtual TaskQueue::Handle CreateTaskQueue(const TaskQueue::Spec& spec) = 0;
// Returns true iff this SequenceManager has no immediate work to do. I.e.
// there are no pending non-delayed tasks or delayed tasks that are due to
// run. This method ignores any pending delayed tasks that might have become
// eligible to run since the last task was executed. This is important because
// if it did tests would become flaky depending on the exact timing of this
// call. This is moderately expensive.
virtual bool IsIdleForTesting() = 0;
// The total number of posted tasks that haven't executed yet.
virtual size_t GetPendingTaskCountForTesting() const = 0;
// Returns a JSON string which describes all pending tasks.
virtual std::string DescribeAllPendingTasks() const = 0;
// While Now() is less than `prioritize_until` we will alternate between a
// SequenceManager task and a yielding to the underlying sequence (e.g., the
// message pump).
virtual void PrioritizeYieldingToNative(base::TimeTicks prioritize_until) = 0;
// Adds an observer which reports task execution. Can only be called on the
// same thread that `this` is running on.
virtual void AddTaskObserver(TaskObserver* task_observer) = 0;
// Removes an observer which reports task execution. Can only be called on the
// same thread that `this` is running on.
virtual void RemoveTaskObserver(TaskObserver* task_observer) = 0;
};
class BASE_EXPORT SequenceManager::Settings::Builder {
public:
Builder();
~Builder();
// Sets the MessagePumpType which is used to create a MessagePump.
Builder& SetMessagePumpType(MessagePumpType message_loop_type);
Builder& SetRandomisedSamplingEnabled(bool randomised_sampling_enabled);
// Sets the TickClock the SequenceManager uses to obtain Now.
Builder& SetTickClock(const TickClock* clock);
// Whether or not queueing timestamp will be added to tasks.
Builder& SetAddQueueTimeToTasks(bool add_queue_time_to_tasks);
// Whether many tasks may run between each check for native work.
Builder& SetCanRunTasksByBatches(bool can_run_tasks_by_batches);
Builder& SetPrioritySettings(PrioritySettings settings);
#if DCHECK_IS_ON()
// Controls task execution logging.
Builder& SetTaskLogging(TaskLogging task_execution_logging);
// Whether or not PostTask will emit a debug log.
Builder& SetLogPostTask(bool log_post_task);
// Whether or not debug logs will be emitted when a delayed task becomes
// eligible to run.
Builder& SetLogTaskDelayExpiry(bool log_task_delay_expiry);
// If not zero this seeds a PRNG used by the task selection logic to choose a
// random TaskQueue for a given priority rather than the TaskQueue with the
// oldest EnqueueOrder.
Builder& SetRandomTaskSelectionSeed(uint64_t random_task_selection_seed);
#endif // DCHECK_IS_ON()
Settings Build();
private:
Settings settings_;
};
// Create SequenceManager using MessageLoop on the current thread.
// Implementation is located in sequence_manager_impl.cc.
// TODO(scheduler-dev): Remove after every thread has a SequenceManager.
BASE_EXPORT std::unique_ptr<SequenceManager>
CreateSequenceManagerOnCurrentThread(SequenceManager::Settings settings);
// Create a SequenceManager using the given MessagePump on the current thread.
// MessagePump instances can be created with
// MessagePump::CreateMessagePumpForType().
BASE_EXPORT std::unique_ptr<SequenceManager>
CreateSequenceManagerOnCurrentThreadWithPump(
std::unique_ptr<MessagePump> message_pump,
SequenceManager::Settings settings = SequenceManager::Settings());
// Create an unbound SequenceManager (typically for a future thread or because
// additional setup is required before binding). The SequenceManager can be
// initialized on the current thread and then needs to be bound and initialized
// on the target thread by calling one of the Bind*() methods.
BASE_EXPORT std::unique_ptr<SequenceManager> CreateUnboundSequenceManager(
SequenceManager::Settings settings = SequenceManager::Settings());
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,522 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_IMPL_H_
#define BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_IMPL_H_
#include <deque>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/base_export.h"
#include "base/callback_list.h"
#include "base/compiler_specific.h"
#include "base/containers/circular_deque.h"
#include "base/debug/crash_logging.h"
#include "base/feature_list.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_pump_type.h"
#include "base/observer_list.h"
#include "base/pending_task.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/synchronization/lock.h"
#include "base/task/current_thread.h"
#include "base/task/sequence_manager/associated_thread_id.h"
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/task/sequence_manager/enqueue_order_generator.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/task_queue.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/task/sequence_manager/task_queue_selector.h"
#include "base/task/sequence_manager/thread_controller.h"
#include "base/task/sequence_manager/work_tracker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/default_tick_clock.h"
#include "base/types/pass_key.h"
#include "base/values.h"
#include "build/build_config.h"
namespace base {
namespace internal {
class SequenceManagerThreadDelegate;
}
namespace trace_event {
class ConvertableToTraceFormat;
} // namespace trace_event
namespace sequence_manager {
class SequenceManagerForTest;
class TaskQueue;
class TaskTimeObserver;
class TimeDomain;
namespace internal {
class TaskQueueImpl;
class DefaultWakeUpQueue;
class SequenceManagerImpl;
class ThreadControllerImpl;
// A private factory method for SequenceManagerThreadDelegate which is
// equivalent to sequence_manager::CreateUnboundSequenceManager() but returns
// the underlying impl.
std::unique_ptr<SequenceManagerImpl> CreateUnboundSequenceManagerImpl(
PassKey<base::internal::SequenceManagerThreadDelegate>,
SequenceManager::Settings settings);
// The task queue manager provides N task queues and a selector interface for
// choosing which task queue to service next. Each task queue consists of two
// sub queues:
//
// 1. Incoming task queue. Tasks that are posted get immediately appended here.
// When a task is appended into an empty incoming queue, the task manager
// work function (DoWork()) is scheduled to run on the main task runner.
//
// 2. Work queue. If a work queue is empty when DoWork() is entered, tasks from
// the incoming task queue (if any) are moved here. The work queues are
// registered with the selector as input to the scheduling decision.
//
class BASE_EXPORT SequenceManagerImpl
: public SequenceManager,
public internal::SequencedTaskSource,
public internal::TaskQueueSelector::Observer,
public RunLoop::NestingObserver {
public:
using Observer = SequenceManager::Observer;
SequenceManagerImpl(const SequenceManagerImpl&) = delete;
SequenceManagerImpl& operator=(const SequenceManagerImpl&) = delete;
~SequenceManagerImpl() override;
// Initializes features for this class. See `base::features::Init()`.
static void InitializeFeatures();
// SequenceManager implementation:
void BindToCurrentThread() override;
scoped_refptr<SequencedTaskRunner> GetTaskRunnerForCurrentTask() override;
void BindToMessagePump(std::unique_ptr<MessagePump> message_pump) override;
void SetObserver(Observer* observer) override;
void AddTaskTimeObserver(TaskTimeObserver* task_time_observer) override;
void RemoveTaskTimeObserver(TaskTimeObserver* task_time_observer) override;
void SetTimeDomain(TimeDomain* time_domain) override;
void ResetTimeDomain() override;
const TickClock* GetTickClock() const override;
TimeTicks NowTicks() const override;
void SetDefaultTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) override;
void ReclaimMemory() override;
bool GetAndClearSystemIsQuiescentBit() override;
void SetWorkBatchSize(int work_batch_size) override;
void EnableCrashKeys(const char* async_stack_crash_key) override;
const MetricRecordingSettings& GetMetricRecordingSettings() const override;
size_t GetPendingTaskCountForTesting() const override;
TaskQueue::Handle CreateTaskQueue(const TaskQueue::Spec& spec) override;
std::string DescribeAllPendingTasks() const override;
void PrioritizeYieldingToNative(base::TimeTicks prioritize_until) override;
void AddTaskObserver(TaskObserver* task_observer) override;
void RemoveTaskObserver(TaskObserver* task_observer) override;
std::optional<WakeUp> GetNextDelayedWakeUp() const override;
TaskQueue::QueuePriority GetPriorityCount() const override;
// SequencedTaskSource implementation:
void SetRunTaskSynchronouslyAllowed(
bool can_run_tasks_synchronously) override;
std::optional<SelectedTask> SelectNextTask(
LazyNow& lazy_now,
SelectTaskOption option = SelectTaskOption::kDefault) override;
void DidRunTask(LazyNow& lazy_now) override;
std::optional<WakeUp> GetPendingWakeUp(
LazyNow* lazy_now,
SelectTaskOption option = SelectTaskOption::kDefault) override;
bool HasPendingHighResolutionTasks() override;
void OnBeginWork() override;
bool OnIdle() override;
void MaybeEmitTaskDetails(
perfetto::EventContext& ctx,
const SequencedTaskSource::SelectedTask& selected_task) const override;
void AddDestructionObserver(
CurrentThread::DestructionObserver* destruction_observer);
void RemoveDestructionObserver(
CurrentThread::DestructionObserver* destruction_observer);
[[nodiscard]] CallbackListSubscription RegisterOnNextIdleCallback(
OnceClosure on_next_idle_callback);
// Sets / returns the default TaskRunner. Thread-safe.
void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
scoped_refptr<SingleThreadTaskRunner> GetTaskRunner();
bool IsBoundToCurrentThread() const;
MessagePump* GetMessagePump() const;
bool IsType(MessagePumpType type) const;
void SetAddQueueTimeToTasks(bool enable);
void SetTaskExecutionAllowedInNativeNestedLoop(bool allowed);
bool IsTaskExecutionAllowedInNativeNestedLoop() const;
#if BUILDFLAG(IS_IOS)
void AttachToMessagePump();
#endif
bool IsIdleForTesting() override;
void EnableMessagePumpTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing = false);
// Requests that a task to process work is scheduled.
void ScheduleWork();
// Returns the currently executing TaskQueue if any. Must be called on the
// thread this class was created on.
internal::TaskQueueImpl* currently_executing_task_queue() const;
// Unregisters a TaskQueue previously created by |NewTaskQueue()|.
// No tasks will run on this queue after this call.
void UnregisterTaskQueueImpl(
std::unique_ptr<internal::TaskQueueImpl> task_queue);
scoped_refptr<AssociatedThreadId> associated_thread() const {
return associated_thread_;
}
const Settings& settings() const LIFETIME_BOUND { return settings_; }
WeakPtr<SequenceManagerImpl> GetWeakPtr();
// How frequently to perform housekeeping tasks (sweeping canceled tasks etc).
static constexpr TimeDelta kReclaimMemoryInterval = Seconds(30);
protected:
static std::unique_ptr<ThreadControllerImpl>
CreateThreadControllerImplForCurrentThread(const TickClock* clock);
// Create a task queue manager where |controller| controls the thread
// on which the tasks are eventually run.
SequenceManagerImpl(std::unique_ptr<internal::ThreadController> controller,
SequenceManager::Settings settings = Settings());
friend class internal::TaskQueueImpl;
friend class internal::DefaultWakeUpQueue;
friend class ::base::sequence_manager::SequenceManagerForTest;
private:
// Returns the SequenceManager running the
// current thread. It must only be used on the thread it was obtained.
// Only to be used by CurrentThread for the moment
static SequenceManagerImpl* GetCurrent();
friend class ::base::CurrentThread;
// Factory friends to call into private creation methods.
friend std::unique_ptr<SequenceManager>
sequence_manager::CreateSequenceManagerOnCurrentThread(
SequenceManager::Settings);
friend std::unique_ptr<SequenceManager>
sequence_manager::CreateSequenceManagerOnCurrentThreadWithPump(
std::unique_ptr<MessagePump> message_pump,
SequenceManager::Settings);
friend std::unique_ptr<SequenceManager>
sequence_manager::CreateUnboundSequenceManager(SequenceManager::Settings);
friend std::unique_ptr<SequenceManagerImpl>
sequence_manager::internal::CreateUnboundSequenceManagerImpl(
PassKey<base::internal::SequenceManagerThreadDelegate>,
SequenceManager::Settings);
// Assume direct control over current thread and create a SequenceManager.
// This function should be called only once per thread.
// This function assumes that a task execution environment is already
// initialized for the current thread.
static std::unique_ptr<SequenceManagerImpl> CreateOnCurrentThread(
SequenceManager::Settings settings);
// Create an unbound SequenceManager (typically for a future thread). The
// SequenceManager can be initialized on the current thread and then needs to
// be bound and initialized on the target thread by calling one of the Bind*()
// methods.
static std::unique_ptr<SequenceManagerImpl> CreateUnbound(
SequenceManager::Settings settings);
enum class ProcessTaskResult {
kDeferred,
kExecuted,
kSequenceManagerDeleted,
};
// SequenceManager maintains a queue of non-nestable tasks since they're
// uncommon and allocating an extra deque per TaskQueue will waste the memory.
using NonNestableTaskDeque =
circular_deque<internal::TaskQueueImpl::DeferredNonNestableTask>;
// We have to track rentrancy because we support nested runloops but the
// selector interface is unaware of those. This struct keeps track off all
// task related state needed to make pairs of SelectNextTask() / DidRunTask()
// work.
struct ExecutingTask {
ExecutingTask(Task&& task,
internal::TaskQueueImpl* task_queue,
TaskQueue::TaskTiming task_timing)
: pending_task(std::move(task)),
task_queue(task_queue),
task_queue_name(task_queue->GetProtoName()),
task_timing(task_timing),
priority(task_queue->GetQueuePriority()),
task_type(pending_task.task_type) {}
Task pending_task;
// `task_queue` is not a raw_ptr<...> for performance reasons (based on
// analysis of sampling profiler data and tab_search:top100:2020).
RAW_PTR_EXCLUSION internal::TaskQueueImpl* task_queue = nullptr;
// Save task_queue_name as the task queue can be deleted within the task.
QueueName task_queue_name;
TaskQueue::TaskTiming task_timing;
// Save priority as it might change after running a task.
TaskQueue::QueuePriority priority;
// Save task metadata to use in after running a task as |pending_task|
// won't be available then.
int task_type;
};
struct MainThreadOnly {
explicit MainThreadOnly(
SequenceManagerImpl* sequence_manager,
const scoped_refptr<AssociatedThreadId>& associated_thread,
const SequenceManager::Settings& settings,
const base::TickClock* clock);
~MainThreadOnly();
int nesting_depth = 0;
NonNestableTaskDeque non_nestable_task_queue;
// TODO(altimin): Switch to instruction pointer crash key when it's
// available.
raw_ptr<debug::CrashKeyString> file_name_crash_key = nullptr;
raw_ptr<debug::CrashKeyString> function_name_crash_key = nullptr;
raw_ptr<debug::CrashKeyString> async_stack_crash_key = nullptr;
std::array<char, static_cast<size_t>(debug::CrashKeySize::Size64)>
async_stack_buffer = {};
std::optional<base::MetricsSubSampler> metrics_subsampler;
internal::TaskQueueSelector selector;
// RAW_PTR_EXCLUSION: Performance reasons(based on analysis of
// speedometer3).
ObserverList<TaskObserver>::UncheckedAndRawPtrExcluded task_observers;
ObserverList<TaskTimeObserver> task_time_observers;
const raw_ptr<const base::TickClock> default_clock;
raw_ptr<TimeDomain> time_domain = nullptr;
std::unique_ptr<WakeUpQueue> wake_up_queue;
std::unique_ptr<WakeUpQueue> non_waking_wake_up_queue;
// If true MaybeReclaimMemory will attempt to reclaim memory.
bool memory_reclaim_scheduled = false;
// Used to ensure we don't perform expensive housekeeping too frequently.
TimeTicks next_time_to_reclaim_memory;
// List of task queues managed by this SequenceManager.
// - active_queues contains queues that are still running tasks, which are
// are owned by relevant TaskQueues.
// - queues_to_delete contains soon-to-be-deleted queues, because some
// internal scheduling code does not expect queues to be pulled
// from underneath.
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of
// speedometer3).
RAW_PTR_EXCLUSION std::set<internal::TaskQueueImpl*> active_queues;
std::map<internal::TaskQueueImpl*, std::unique_ptr<internal::TaskQueueImpl>>
queues_to_delete;
bool task_was_run_on_quiescence_monitored_queue = false;
bool nesting_observer_registered_ = false;
// Use std::deque() so that references returned by SelectNextTask() remain
// valid until the matching call to DidRunTask(), even when nested RunLoops
// cause tasks to be pushed on the stack in-between. This is needed because
// references are kept in local variables by calling code between
// SelectNextTask()/DidRunTask().
std::deque<ExecutingTask> task_execution_stack;
raw_ptr<Observer> observer = nullptr; // NOT OWNED
ObserverList<CurrentThread::DestructionObserver>::
UncheckedAndDanglingUntriaged destruction_observers;
// Notified the next time `OnIdle()` completes without scheduling additional
// work.
OnceClosureList on_next_idle_callbacks;
};
void CompleteInitializationOnBoundThread();
// TaskQueueSelector::Observer:
void OnTaskQueueEnabled(internal::TaskQueueImpl* queue) override;
void OnWorkAvailable() override;
// RunLoop::NestingObserver:
void OnBeginNestedRunLoop() override;
void OnExitNestedRunLoop() override;
// Schedules next wake-up at the given time, canceling any previous requests.
// Use std::nullopt to cancel a wake-up. Must be called on the thread this
// class was created on.
void SetNextWakeUp(LazyNow* lazy_now, std::optional<WakeUp> wake_up);
// Called before TaskQueue requests to reload its empty immediate work queue.
void WillRequestReloadImmediateWorkQueue();
// Returns a valid `SyncWorkAuthorization` if a call to `RunOrPostTask` on a
// `SequencedTaskRunner` bound to this `SequenceManager` may run its task
// synchronously.
SyncWorkAuthorization TryAcquireSyncWorkAuthorization();
// Called when a task is about to be queued. May add metadata to the task and
// emit trace events.
void WillQueueTask(Task* pending_task);
// Enqueues onto delayed WorkQueues all delayed tasks which must run now
// (cannot be postponed) and possibly some delayed tasks which can run now but
// could be postponed (due to how tasks are stored, it is not possible to
// retrieve all such tasks efficiently) and reloads any empty work queues.
void MoveReadyDelayedTasksToWorkQueues(LazyNow* lazy_now);
void NotifyWillProcessTask(ExecutingTask* task, LazyNow* time_before_task);
void NotifyDidProcessTask(ExecutingTask* task, LazyNow* time_after_task);
EnqueueOrder GetNextSequenceNumber();
bool GetAddQueueTimeToTasks();
std::unique_ptr<trace_event::ConvertableToTraceFormat>
AsValueWithSelectorResultForTracing(internal::WorkQueue* selected_work_queue,
bool force_verbose) const;
Value::Dict AsValueWithSelectorResult(
internal::WorkQueue* selected_work_queue,
bool force_verbose) const;
// Used in construction of TaskQueueImpl to obtain an AtomicFlag which it can
// use to request reload by ReloadEmptyWorkQueues. The lifetime of
// TaskQueueImpl is managed by this class and the handle will be released by
// TaskQueueImpl::UnregisterTaskQueue which is always called before the
// queue's destruction.
AtomicFlagSet::AtomicFlag GetFlagToRequestReloadForEmptyQueue(
TaskQueueImpl* task_queue);
// Calls |TakeImmediateIncomingQueueTasks| on all queues with their reload
// flag set in |empty_queues_to_reload_|.
void ReloadEmptyWorkQueues();
std::unique_ptr<internal::TaskQueueImpl> CreateTaskQueueImpl(
const TaskQueue::Spec& spec);
// Periodically reclaims memory by sweeping away canceled tasks and shrinking
// buffers.
void MaybeReclaimMemory();
// Deletes queues marked for deletion and empty queues marked for shutdown.
void CleanUpQueues();
// Removes canceled delayed tasks from the front of wake up queue.
void RemoveAllCanceledDelayedTasksFromFront(LazyNow* lazy_now);
TaskQueue::TaskTiming::TimeRecordingPolicy ShouldRecordTaskTiming(
const internal::TaskQueueImpl* task_queue);
bool ShouldRecordCPUTimeForTask();
// Write the async stack trace onto a crash key as whitespace-delimited hex
// addresses.
void RecordCrashKeys(const PendingTask&);
// Helper to terminate all scoped trace events to allow starting new ones
// in SelectNextTask().
std::optional<SelectedTask> SelectNextTaskImpl(LazyNow& lazy_now,
SelectTaskOption option);
// Returns a wake-up for the next delayed task which is not ripe for
// execution, or nullopt if `option` is `kSkipDelayedTask` or there
// are no such tasks (immediate tasks don't count).
std::optional<WakeUp> GetNextDelayedWakeUpWithOption(
SelectTaskOption option) const;
// Given a `wake_up` describing when the next delayed task should run, returns
// a wake up that should be scheduled on the thread. `is_immediate()` if the
// wake up should run immediately. `nullopt` if no wake up is required because
// `wake_up` is `nullopt` or a `time_domain` is used.
std::optional<WakeUp> AdjustWakeUp(std::optional<WakeUp> wake_up,
LazyNow* lazy_now) const;
void MaybeAddLeewayToTask(Task& task) const;
#if DCHECK_IS_ON()
void LogTaskDebugInfo(const internal::WorkQueue* work_queue) const;
#endif
// Determines if wall time or thread time should be recorded for the next
// task.
TaskQueue::TaskTiming InitializeTaskTiming(
internal::TaskQueueImpl* task_queue);
const scoped_refptr<AssociatedThreadId> associated_thread_;
EnqueueOrderGenerator enqueue_order_generator_;
const std::unique_ptr<internal::ThreadController> controller_;
const Settings settings_;
const MetricRecordingSettings metric_recording_settings_;
WorkTracker work_tracker_;
// Whether to add the queue time to tasks.
base::subtle::Atomic32 add_queue_time_to_tasks_;
AtomicFlagSet empty_queues_to_reload_;
MainThreadOnly main_thread_only_;
MainThreadOnly& main_thread_only() {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
return main_thread_only_;
}
const MainThreadOnly& main_thread_only() const LIFETIME_BOUND {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
return main_thread_only_;
}
// |clock_| either refers to the TickClock representation of |time_domain|
// (same object) if any, or to |default_clock| otherwise. It is maintained as
// an atomic pointer here for multi-threaded usage.
std::atomic<const base::TickClock*> clock_;
const base::TickClock* main_thread_clock() const {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
return clock_.load(std::memory_order_relaxed);
}
const base::TickClock* any_thread_clock() const {
// |memory_order_acquire| matched by |memory_order_release| in
// SetTimeDomain() to ensure all data used by |clock_| is visible when read
// from the current thread. A thread might try to access a stale |clock_|
// but that's not an issue since |time_domain| contractually outlives
// SequenceManagerImpl even if it's reset.
return clock_.load(std::memory_order_acquire);
}
WeakPtrFactory<SequenceManagerImpl> weak_factory_{this};
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_IMPL_H_

View File

@@ -0,0 +1,27 @@
// Copyright 2021 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/sequenced_task_source.h"
namespace base {
namespace sequence_manager {
namespace internal {
SequencedTaskSource::SelectedTask::SelectedTask(const SelectedTask&) = default;
SequencedTaskSource::SelectedTask::SelectedTask(
Task& task,
TaskExecutionTraceLogger task_execution_trace_logger,
TaskQueue::QueuePriority priority,
QueueName task_queue_name)
: task(task),
task_execution_trace_logger(task_execution_trace_logger),
priority(priority),
task_queue_name(task_queue_name) {}
SequencedTaskSource::SelectedTask::~SelectedTask() = default;
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,107 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_SEQUENCED_TASK_SOURCE_H_
#define BASE_TASK_SEQUENCE_MANAGER_SEQUENCED_TASK_SOURCE_H_
#include <optional>
#include "base/base_export.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/pending_task.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/task_queue.h"
#include "base/task/sequence_manager/tasks.h"
namespace perfetto {
class EventContext;
}
namespace base {
namespace sequence_manager {
namespace internal {
// Interface to pass tasks to ThreadController.
class SequencedTaskSource {
public:
enum class SelectTaskOption { kDefault, kSkipDelayedTask };
using TaskExecutionTraceLogger =
RepeatingCallback<void(perfetto::EventContext&, const Task&)>;
struct BASE_EXPORT SelectedTask {
SelectedTask(const SelectedTask&);
SelectedTask(Task& task,
TaskExecutionTraceLogger task_execution_trace_logger,
TaskQueue::QueuePriority priority,
QueueName task_queue_name);
~SelectedTask();
// RAW_PTR_EXCLUSION: Performance reasons: based on this sampling profiler
// result on Mac. go/brp-mac-prof-diff-20230403
RAW_PTR_EXCLUSION Task& task;
// Callback to fill trace event arguments associated with the task
// execution. Can be null
TaskExecutionTraceLogger task_execution_trace_logger =
TaskExecutionTraceLogger();
TaskQueue::QueuePriority priority;
QueueName task_queue_name;
};
virtual ~SequencedTaskSource() = default;
// Controls whether a `SequencedTaskRunner` associated with this source can
// run a task synchronously in `RunOrPostTask`. Enable this to indicate that
// there isn't any pending or running work that has mutual exclusion or
// ordering expectations with tasks from this source, outside of
// `SelectNextTask()` or `OnBeginWork()` -> `OnIdle()` (those prevent tasks
// from running synchronously, irrespective of the state set here).
virtual void SetRunTaskSynchronouslyAllowed(
bool can_run_tasks_synchronously) = 0;
// Returns the next task to run from this source or nullopt if
// there're no more tasks ready to run. If a task is returned,
// DidRunTask() must be invoked before the next call to SelectNextTask().
// |option| allows control on which kind of tasks can be selected.
virtual std::optional<SelectedTask> SelectNextTask(
LazyNow& lazy_now,
SelectTaskOption option = SelectTaskOption::kDefault) = 0;
// Notifies this source that the task previously obtained
// from SelectNextTask() has been completed.
virtual void DidRunTask(LazyNow& lazy_now) = 0;
// Returns a WakeUp for the next pending task, is_immediate() if the
// next task can run immediately, or nullopt if there are no more immediate or
// delayed tasks. |option| allows control on which kind of tasks can be
// selected. May delete canceled tasks.
virtual std::optional<WakeUp> GetPendingWakeUp(
LazyNow* lazy_now,
SelectTaskOption option = SelectTaskOption::kDefault) = 0;
// Return true if there are any pending tasks in the task source which require
// high resolution timing.
virtual bool HasPendingHighResolutionTasks() = 0;
// Indicates that work that has mutual exclusion expectations with tasks from
// this `SequencedTaskSource` will start running.
virtual void OnBeginWork() = 0;
// Called when we have run out of immediate work. If more immediate work
// becomes available as a result of any processing done by this callback,
// return true to schedule a future DoWork.
virtual bool OnIdle() = 0;
// Called prior to running `selected_task` to emit trace event data for it.
virtual void MaybeEmitTaskDetails(
perfetto::EventContext& ctx,
const SelectedTask& selected_task) const = 0;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_SEQUENCED_TASK_SOURCE_H_

View File

@@ -0,0 +1,89 @@
// Copyright 2021 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/task_order.h"
#include <functional>
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/task/sequence_manager/tasks.h"
namespace base {
namespace sequence_manager {
namespace {
// Returns true iff `task_order1` Comparator{} `task_order2`. Used to
// implement other comparison operators.
template <typename Comparator>
static bool Compare(const base::sequence_manager::TaskOrder& task_order1,
const base::sequence_manager::TaskOrder& task_order2) {
Comparator cmp{};
if (task_order1.enqueue_order() != task_order2.enqueue_order())
return cmp(task_order1.enqueue_order(), task_order2.enqueue_order());
if (task_order1.delayed_run_time() != task_order2.delayed_run_time())
return cmp(task_order1.delayed_run_time(), task_order2.delayed_run_time());
// If the times happen to match, then we use the sequence number to decide.
// Compare the difference to support integer roll-over.
return cmp(task_order1.sequence_num() - task_order2.sequence_num(), 0);
}
} // namespace
// Static
TaskOrder TaskOrder::CreateForTesting(EnqueueOrder enqueue_order,
TimeTicks delayed_run_time,
int sequence_num) {
return TaskOrder(enqueue_order, delayed_run_time, sequence_num);
}
// Static
TaskOrder TaskOrder::CreateForTesting(EnqueueOrder enqueue_order) {
return TaskOrder(enqueue_order, TimeTicks(), 0);
}
TaskOrder::TaskOrder(EnqueueOrder enqueue_order,
TimeTicks delayed_run_time,
int sequence_num)
: enqueue_order_(enqueue_order),
delayed_run_time_(delayed_run_time),
sequence_num_(sequence_num) {}
TaskOrder::TaskOrder(const TaskOrder& other) = default;
TaskOrder& TaskOrder::operator=(const TaskOrder& other) = default;
TaskOrder::~TaskOrder() = default;
bool TaskOrder::operator>(const TaskOrder& other) const {
return Compare<std::greater<>>(*this, other);
}
bool TaskOrder::operator<(const TaskOrder& other) const {
return Compare<std::less<>>(*this, other);
}
bool TaskOrder::operator<=(const TaskOrder& other) const {
return Compare<std::less_equal<>>(*this, other);
}
bool TaskOrder::operator>=(const TaskOrder& other) const {
return Compare<std::greater_equal<>>(*this, other);
}
bool TaskOrder::operator==(const TaskOrder& other) const {
return enqueue_order_ == other.enqueue_order_ &&
delayed_run_time_ == other.delayed_run_time_ &&
sequence_num_ == other.sequence_num_;
}
bool TaskOrder::operator!=(const TaskOrder& other) const {
return !(*this == other);
}
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,91 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_ORDER_H_
#define BASE_TASK_SEQUENCE_MANAGER_TASK_ORDER_H_
#include "base/base_export.h"
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/time/time.h"
namespace base {
namespace sequence_manager {
struct Task;
namespace internal {
class Fence;
} // namespace internal
// `TaskOrder` represents the order of a `Task` relative to other `Task`s. The <
// operator on the set of all `TaskOrder`s is a strict total ordering [1].
// `TaskOrder` consists of the following:
// - `enqueue_order_`: The order the task was enqueued. It is assigned at
// posting time for immediate tasks and enqueue time for delayed tasks, which
// is the time at which a pending delayed task is moved to its `WorkQueue`
// (after its delay has expired, during a wake-up). This is the primary
// ordering for tasks. Delayed tasks that are enqueued during the same
// wake-up have the same `enqueue_order_` and their order is decided by
// `delayed_run_time_` and `sequence_num_`.
//
// - `delayed_run_time_`: The latest time at which a delayed task should run;
// only non-zero for delayed tasks. Before they become ripe, delayed tasks
// are maintained in a heap ordered by `latest_delayed_run_time`.
//
// - `sequence_num_`: a strictly increasing number assigned at posting time for
// all tasks. This is used to order delayed tasks if their `enqueue_order_`
// and `delayed_run_time_`s match.
//
// While `TaskOrder` can be used to order a set `Task`s, it is not necessarily
// the order that the associated tasks will run: tasks are executed in order of
// highest to lowest priority, tasks from disabled queues and queues blocked by
// fences are prevented from running, and sequence manager may choose immediate
// over delayed tasks to prevent starvation.
//
// [1] sequence_num is an int rollovers are possible, however it is extremely
// unlikely that two delayed tasks would have the same posting order and delayed
// run time.
class BASE_EXPORT TaskOrder {
public:
TaskOrder(const TaskOrder& other);
TaskOrder& operator=(const TaskOrder& other);
~TaskOrder();
EnqueueOrder enqueue_order() const { return enqueue_order_; }
int sequence_num() const { return sequence_num_; }
// TODO(crbug.com/40158967): Rename to latest_delayed_run_time() for clarity.
TimeTicks delayed_run_time() const { return delayed_run_time_; }
static TaskOrder CreateForTesting(EnqueueOrder enqueue_order,
TimeTicks delayed_run_time,
int sequence_num);
static TaskOrder CreateForTesting(EnqueueOrder enqueue_order);
bool operator>(const TaskOrder& other) const;
bool operator<(const TaskOrder& other) const;
bool operator<=(const TaskOrder& other) const;
bool operator>=(const TaskOrder& other) const;
bool operator==(const TaskOrder& other) const;
bool operator!=(const TaskOrder& other) const;
protected:
TaskOrder(EnqueueOrder enqueue_order,
TimeTicks delayed_run_time,
int sequence_num);
private:
friend class internal::Fence;
friend struct Task;
EnqueueOrder enqueue_order_;
TimeTicks delayed_run_time_;
int sequence_num_;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_ORDER_H_

View File

@@ -0,0 +1,105 @@
// Copyright 2017 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/task_queue.h"
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequence_manager/associated_thread_id.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_checker_impl.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
namespace base::sequence_manager {
TaskQueue::QueueEnabledVoter::QueueEnabledVoter(
WeakPtr<internal::TaskQueueImpl> task_queue)
: task_queue_(std::move(task_queue)) {
task_queue_->AddQueueEnabledVoter(enabled_, *this);
}
TaskQueue::QueueEnabledVoter::~QueueEnabledVoter() {
if (task_queue_) {
task_queue_->RemoveQueueEnabledVoter(enabled_, *this);
}
}
void TaskQueue::QueueEnabledVoter::SetVoteToEnable(bool enabled) {
if (enabled == enabled_) {
return;
}
enabled_ = enabled;
if (task_queue_) {
task_queue_->OnQueueEnabledVoteChanged(enabled_);
}
}
TaskQueue::TaskTiming::TaskTiming(bool has_wall_time, bool has_thread_time)
: has_wall_time_(has_wall_time), has_thread_time_(has_thread_time) {}
void TaskQueue::TaskTiming::RecordTaskStart(LazyNow* now) {
DCHECK_EQ(State::NotStarted, state_);
state_ = State::Running;
if (has_wall_time())
start_time_ = now->Now();
if (has_thread_time())
start_thread_time_ = base::ThreadTicks::Now();
}
void TaskQueue::TaskTiming::RecordTaskEnd(LazyNow* now) {
DCHECK(state_ == State::Running || state_ == State::Finished);
if (state_ == State::Finished)
return;
state_ = State::Finished;
if (has_wall_time())
end_time_ = now->Now();
if (has_thread_time())
end_thread_time_ = base::ThreadTicks::Now();
}
TaskQueue::Handle::Handle(std::unique_ptr<internal::TaskQueueImpl> task_queue)
: task_queue_(std::move(task_queue)),
sequence_manager_(task_queue_->GetSequenceManagerWeakPtr()) {}
TaskQueue::Handle::Handle() = default;
TaskQueue::Handle::~Handle() {
reset();
}
TaskQueue* TaskQueue::Handle::get() const {
return task_queue_.get();
}
TaskQueue* TaskQueue::Handle::operator->() const {
return task_queue_.get();
}
void TaskQueue::Handle::reset() {
if (!task_queue_) {
return;
}
// Sequence manager already unregistered the task queue.
if (task_queue_->IsUnregistered()) {
task_queue_.reset();
return;
}
CHECK(sequence_manager_);
sequence_manager_->UnregisterTaskQueueImpl(std::move(task_queue_));
}
TaskQueue::Handle::Handle(TaskQueue::Handle&& other) = default;
TaskQueue::Handle& TaskQueue::Handle::operator=(TaskQueue::Handle&&) = default;
} // namespace base::sequence_manager

View File

@@ -0,0 +1,446 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_
#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <type_traits>
#include "base/base_export.h"
#include "base/check.h"
#include "base/task/common/checked_lock.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/tasks.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_observer.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
#include "base/trace_event/base_tracing_forward.h"
namespace perfetto {
class EventContext;
}
namespace base {
class TaskObserver;
namespace sequence_manager {
using QueueName = ::perfetto::protos::pbzero::SequenceManagerTask::QueueName;
namespace internal {
class SequenceManagerImpl;
class TaskQueueImpl;
} // namespace internal
// A `TaskQueue` represents an ordered list of tasks sharing common properties,
// e.g. priority, throttling, etc. `TaskQueue`s are associated with a
// `SequenceManager` instance, which chooses the next task from its set of
// queues. `TaskQueue`s should typically be used on a single thread since most
// methods are not thread safe (enforeced via CHECKs), but cross-thread task
// posting is supported with thread-safe task runners.
//
// A `TaskQueue` is unregistered (stops accepting and running tasks) when either
// its associated `TaskQueue::Handle` or `SequenceManager` is destroyed. If the
// handle is destroyed while the `SequenceManager` is still alive, the
// `SequenceManager` takes ownership of the queue and schedules it for deletion
// after the current task finishes. Otherwise, if the handle outlives the
// sequence manager, the queue is destroyed when the handle is destroyed.
class BASE_EXPORT TaskQueue {
public:
// Interface that lets a task queue be throttled by changing the wake up time
// and optionally, by inserting fences. A wake up in this context is a
// notification at a given time that lets this TaskQueue know of newly ripe
// delayed tasks if it's enabled. By delaying the desired wake up time to a
// different allowed wake up time, the Throttler can hold off delayed tasks
// that would otherwise by allowed to run sooner.
class BASE_EXPORT Throttler {
public:
// Invoked when the TaskQueue's next allowed wake up time is reached and is
// enabled, even if blocked by a fence. That wake up is defined by the last
// value returned from GetNextAllowedWakeUp().
// This is always called on the thread this TaskQueue is associated with.
virtual void OnWakeUp(LazyNow* lazy_now) = 0;
// Invoked when the TaskQueue newly gets a pending immediate task and is
// enabled, even if blocked by a fence. Redundant calls are possible when
// the TaskQueue already had a pending immediate task.
// The implementation may use this to:
// - Restrict task execution by inserting/updating a fence.
// - Update the TaskQueue's next delayed wake up via UpdateWakeUp().
// This allows the Throttler to perform additional operations later from
// OnWakeUp().
// This is always called on the thread this TaskQueue is associated with.
virtual void OnHasImmediateTask() = 0;
// Invoked when the TaskQueue is enabled and wants to know when to schedule
// the next delayed wake-up (which happens at least every time this queue is
// about to cause the next wake up) provided |next_desired_wake_up|, the
// wake-up for the next pending delayed task in this queue (pending delayed
// tasks that are ripe may be ignored), or nullopt if there's no pending
// delayed task. |has_ready_task| indicates whether there are immediate
// tasks or ripe delayed tasks. The implementation should return the next
// allowed wake up, or nullopt if no future wake-up is necessary.
// This is always called on the thread this TaskQueue is associated with.
virtual std::optional<WakeUp> GetNextAllowedWakeUp(
LazyNow* lazy_now,
std::optional<WakeUp> next_desired_wake_up,
bool has_ready_task) = 0;
protected:
~Throttler() = default;
};
// Wrapper around a `TaskQueue`, exposed by `SequenceManager` when creating a
// task queue. The handle owns the underlying queue and exposes it through a
// unique_ptr-like interface, and it's responsible for managing the queue's
// lifetime, ensuring the queue is properly unregistered with the queue's
// `SequenceManager` when the handle is destroyed.
class BASE_EXPORT Handle {
public:
Handle();
Handle(Handle&&);
Handle& operator=(Handle&&);
~Handle();
void reset();
TaskQueue* get() const;
TaskQueue* operator->() const;
explicit operator bool() const { return !!task_queue_; }
private:
friend class internal::SequenceManagerImpl;
explicit Handle(std::unique_ptr<internal::TaskQueueImpl> task_queue);
std::unique_ptr<internal::TaskQueueImpl> task_queue_;
WeakPtr<internal::SequenceManagerImpl> sequence_manager_;
};
// Queues with higher priority (smaller number) are selected to run before
// queues of lower priority. Note that there is no starvation protection,
// i.e., a constant stream of high priority work can mean that tasks in lower
// priority queues won't get to run.
using QueuePriority = uint8_t;
// By default there is only a single priority. Sequences making use of
// priorities should parameterize the `SequenceManager` with the appropriate
// `SequenceManager::PrioritySettings`.
enum class DefaultQueuePriority : QueuePriority {
kNormalPriority = 0,
// Must be the last entry.
kQueuePriorityCount = 1,
};
// Options for constructing a TaskQueue.
struct Spec {
explicit Spec(QueueName name) : name(name) {}
Spec SetShouldMonitorQuiescence(bool should_monitor) {
should_monitor_quiescence = should_monitor;
return *this;
}
Spec SetShouldNotifyObservers(bool run_observers) {
should_notify_observers = run_observers;
return *this;
}
// Delayed fences require Now() to be sampled when posting immediate tasks
// which is not free.
Spec SetDelayedFencesAllowed(bool allow_delayed_fences) {
delayed_fence_allowed = allow_delayed_fences;
return *this;
}
Spec SetNonWaking(bool non_waking_in) {
non_waking = non_waking_in;
return *this;
}
QueueName name;
bool should_monitor_quiescence = false;
bool should_notify_observers = true;
bool delayed_fence_allowed = false;
bool non_waking = false;
};
// Information about task execution.
//
// Wall-time related methods (start_time, end_time, wall_duration) can be
// called only when |has_wall_time()| is true.
// Thread-time related mehtods (start_thread_time, end_thread_time,
// thread_duration) can be called only when |has_thread_time()| is true.
//
// start_* should be called after RecordTaskStart.
// end_* and *_duration should be called after RecordTaskEnd.
class BASE_EXPORT TaskTiming {
public:
enum class State { NotStarted, Running, Finished };
enum class TimeRecordingPolicy { DoRecord, DoNotRecord };
TaskTiming(bool has_wall_time, bool has_thread_time);
bool has_wall_time() const { return has_wall_time_; }
bool has_thread_time() const { return has_thread_time_; }
base::TimeTicks start_time() const {
DCHECK(has_wall_time());
return start_time_;
}
base::TimeTicks end_time() const {
DCHECK(has_wall_time());
return end_time_;
}
base::TimeDelta wall_duration() const {
DCHECK(has_wall_time());
return end_time_ - start_time_;
}
base::ThreadTicks start_thread_time() const {
DCHECK(has_thread_time());
return start_thread_time_;
}
base::ThreadTicks end_thread_time() const {
DCHECK(has_thread_time());
return end_thread_time_;
}
base::TimeDelta thread_duration() const {
DCHECK(has_thread_time());
return end_thread_time_ - start_thread_time_;
}
State state() const { return state_; }
void RecordTaskStart(LazyNow* now);
void RecordTaskEnd(LazyNow* now);
// Protected for tests.
protected:
State state_ = State::NotStarted;
bool has_wall_time_;
bool has_thread_time_;
base::TimeTicks start_time_;
base::TimeTicks end_time_;
base::ThreadTicks start_thread_time_;
base::ThreadTicks end_thread_time_;
};
// An interface that lets the owner vote on whether or not the associated
// TaskQueue should be enabled.
class BASE_EXPORT QueueEnabledVoter {
public:
~QueueEnabledVoter();
QueueEnabledVoter(const QueueEnabledVoter&) = delete;
const QueueEnabledVoter& operator=(const QueueEnabledVoter&) = delete;
// Votes to enable or disable the associated TaskQueue. The TaskQueue will
// only be enabled if all the voters agree it should be enabled, or if there
// are no voters. Voters don't keep the queue alive.
// NOTE this must be called on the thread the associated TaskQueue was
// created on.
void SetVoteToEnable(bool enabled);
bool IsVotingToEnable() const { return enabled_; }
private:
friend class internal::TaskQueueImpl;
explicit QueueEnabledVoter(WeakPtr<internal::TaskQueueImpl> task_queue);
WeakPtr<internal::TaskQueueImpl> task_queue_;
bool enabled_ = true;
};
TaskQueue(const TaskQueue&) = delete;
TaskQueue& operator=(const TaskQueue&) = delete;
virtual ~TaskQueue() = default;
// Returns an interface that allows the caller to vote on whether or not this
// TaskQueue is enabled. The TaskQueue will be enabled if there are no voters
// or if all agree it should be enabled.
// NOTE this must be called on the thread this TaskQueue was created by.
virtual std::unique_ptr<QueueEnabledVoter> CreateQueueEnabledVoter() = 0;
// NOTE this must be called on the thread this TaskQueue was created by.
virtual bool IsQueueEnabled() const = 0;
// Returns true if the queue is completely empty.
virtual bool IsEmpty() const = 0;
// Returns the number of pending tasks in the queue.
virtual size_t GetNumberOfPendingTasks() const = 0;
// Returns true iff this queue has immediate tasks or delayed tasks that are
// ripe for execution. Ignores the queue's enabled state and fences.
// NOTE: this must be called on the thread this TaskQueue was created by.
// TODO(etiennep): Rename to HasReadyTask() and add LazyNow parameter.
virtual bool HasTaskToRunImmediatelyOrReadyDelayedTask() const = 0;
// Returns a wake-up for the next pending delayed task (pending delayed tasks
// that are ripe may be ignored), ignoring Throttler is any. If there are no
// such tasks (immediate tasks don't count) or the queue is disabled it
// returns nullopt.
// NOTE: this must be called on the thread this TaskQueue was created by.
virtual std::optional<WakeUp> GetNextDesiredWakeUp() = 0;
// Can be called on any thread.
virtual const char* GetName() const = 0;
// Set the priority of the queue to |priority|. NOTE this must be called on
// the thread this TaskQueue was created by.
virtual void SetQueuePriority(QueuePriority priority) = 0;
// Same as above but with an enum value as the priority.
template <typename T, typename = typename std::enable_if_t<std::is_enum_v<T>>>
void SetQueuePriority(T priority) {
static_assert(std::is_same_v<std::underlying_type_t<T>, QueuePriority>,
"Enumerated priorites must have the same underlying type as "
"TaskQueue::QueuePriority");
SetQueuePriority(static_cast<QueuePriority>(priority));
}
// Returns the current queue priority.
virtual QueuePriority GetQueuePriority() const = 0;
// These functions can only be called on the same thread that the task queue
// manager executes its tasks on.
virtual void AddTaskObserver(TaskObserver* task_observer) = 0;
virtual void RemoveTaskObserver(TaskObserver* task_observer) = 0;
enum class InsertFencePosition {
kNow, // Tasks posted on the queue up till this point further may run.
// All further tasks are blocked.
kBeginningOfTime, // No tasks posted on this queue may run.
};
// Inserts a barrier into the task queue which prevents tasks with an enqueue
// order greater than the fence from running until either the fence has been
// removed or a subsequent fence has unblocked some tasks within the queue.
// Note: delayed tasks get their enqueue order set once their delay has
// expired, and non-delayed tasks get their enqueue order set when posted.
//
// Fences come in three flavours:
// - Regular (InsertFence(NOW)) - all tasks posted after this moment
// are blocked.
// - Fully blocking (InsertFence(kBeginningOfTime)) - all tasks including
// already posted are blocked.
// - Delayed (InsertFenceAt(timestamp)) - blocks all tasks posted after given
// point in time (must be in the future).
//
// Only one fence can be scheduled at a time. Inserting a new fence
// will automatically remove the previous one, regardless of fence type.
virtual void InsertFence(InsertFencePosition position) = 0;
// Delayed fences are only allowed for queues created with
// SetDelayedFencesAllowed(true) because this feature implies sampling Now()
// (which isn't free) for every PostTask, even those with zero delay.
virtual void InsertFenceAt(TimeTicks time) = 0;
// Removes any previously added fence and unblocks execution of any tasks
// blocked by it.
virtual void RemoveFence() = 0;
// Returns true if the queue has a fence but it isn't necessarily blocking
// execution of tasks (it may be the case if tasks enqueue order hasn't
// reached the number set for a fence).
virtual bool HasActiveFence() = 0;
// Returns true if the queue has a fence which is blocking execution of tasks.
virtual bool BlockedByFence() const = 0;
// Associates |throttler| to this queue. Only one throttler can be associated
// with this queue. |throttler| must outlive this TaskQueue, or remain valid
// until ResetThrottler().
virtual void SetThrottler(Throttler* throttler) = 0;
// Disassociates the current throttler from this queue, if any.
virtual void ResetThrottler() = 0;
// Updates the task queue's next wake up time in its time domain, taking into
// account the desired run time of queued tasks and policies enforced by the
// throttler if any.
virtual void UpdateWakeUp(LazyNow* lazy_now) = 0;
// Controls whether or not the queue will emit traces events when tasks are
// posted to it while disabled. This only applies for the current or next
// period during which the queue is disabled. When the queue is re-enabled
// this will revert back to the default value of false.
virtual void SetShouldReportPostedTasksWhenDisabled(bool should_report) = 0;
// Create a task runner for this TaskQueue which will annotate all
// posted tasks with the given task type.
// Must be called on the thread this task queue is associated with.
//
// NOTE: Task runners don't keep the TaskQueue alive, so task queues can be
// deleted with valid task runners. Posting a task in that case will fail.
virtual scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner(
TaskType task_type) const = 0;
// Default task runner which doesn't annotate tasks with a task type.
virtual const scoped_refptr<SingleThreadTaskRunner>& task_runner() const = 0;
using OnTaskStartedHandler =
RepeatingCallback<void(const Task&, const TaskQueue::TaskTiming&)>;
using OnTaskCompletedHandler =
RepeatingCallback<void(const Task&, TaskQueue::TaskTiming*, LazyNow*)>;
using OnTaskPostedHandler = RepeatingCallback<void(const Task&)>;
using TaskExecutionTraceLogger =
RepeatingCallback<void(perfetto::EventContext&, const Task&)>;
// Sets a handler to subscribe for notifications about started and completed
// tasks.
virtual void SetOnTaskStartedHandler(OnTaskStartedHandler handler) = 0;
// |task_timing| may be passed in Running state and may not have the end time,
// so that the handler can run an additional task that is counted as a part of
// the main task.
// The handler can call TaskTiming::RecordTaskEnd, which is optional, to
// finalize the task, and use the resulting timing.
virtual void SetOnTaskCompletedHandler(OnTaskCompletedHandler handler) = 0;
// RAII handle associated with an OnTaskPostedHandler. Unregisters the handler
// upon destruction.
class OnTaskPostedCallbackHandle {
public:
OnTaskPostedCallbackHandle(const OnTaskPostedCallbackHandle&) = delete;
OnTaskPostedCallbackHandle& operator=(const OnTaskPostedCallbackHandle&) =
delete;
virtual ~OnTaskPostedCallbackHandle() = default;
protected:
OnTaskPostedCallbackHandle() = default;
};
// Add a callback for adding custom functionality for processing posted task.
// Callback will be dispatched while holding a scheduler lock. As a result,
// callback should not call scheduler APIs directly, as this can lead to
// deadlocks. For example, PostTask should not be called directly and
// ScopedDeferTaskPosting::PostOrDefer should be used instead. `handler` must
// not be a null callback. Must be called on the thread this task queue is
// associated with, and the handle returned must be destroyed on the same
// thread.
[[nodiscard]] virtual std::unique_ptr<OnTaskPostedCallbackHandle>
AddOnTaskPostedHandler(OnTaskPostedHandler handler) = 0;
// Set a callback to fill trace event arguments associated with the task
// execution.
virtual void SetTaskExecutionTraceLogger(TaskExecutionTraceLogger logger) = 0;
protected:
TaskQueue() = default;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,631 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_IMPL_H_
#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_IMPL_H_
#include <stddef.h>
#include <functional>
#include <memory>
#include <optional>
#include <queue>
#include <set>
#include <utility>
#include <vector>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/containers/intrusive_heap.h"
#include "base/dcheck_is_on.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/pending_task.h"
#include "base/task/common/checked_lock.h"
#include "base/task/common/operations_controller.h"
#include "base/task/sequence_manager/associated_thread_id.h"
#include "base/task/sequence_manager/atomic_flag_set.h"
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/task/sequence_manager/fence.h"
#include "base/task/sequence_manager/lazily_deallocated_deque.h"
#include "base/task/sequence_manager/sequenced_task_source.h"
#include "base/task/sequence_manager/task_queue.h"
#include "base/task/sequence_manager/tasks.h"
#include "base/threading/thread_checker.h"
#include "base/time/time_override.h"
#include "base/trace_event/base_tracing_forward.h"
#include "base/values.h"
namespace base {
class LazyNow;
namespace sequence_manager::internal {
class SequenceManagerImpl;
class WorkQueue;
class WorkQueueSets;
class WakeUpQueue;
// TaskQueueImpl has four main queues:
//
// Immediate (non-delayed) tasks:
// |immediate_incoming_queue| - PostTask enqueues tasks here.
// |immediate_work_queue| - SequenceManager takes immediate tasks here.
//
// Delayed tasks
// |delayed_incoming_queue| - PostDelayedTask enqueues tasks here.
// |delayed_work_queue| - SequenceManager takes delayed tasks here.
//
// The |immediate_incoming_queue| can be accessed from any thread, the other
// queues are main-thread only. To reduce the overhead of locking,
// |immediate_work_queue| is swapped with |immediate_incoming_queue| when
// |immediate_work_queue| becomes empty.
//
// Delayed tasks are initially posted to |delayed_incoming_queue| and a wake-up
// is scheduled with the TimeDomain. When the delay has elapsed, the TimeDomain
// calls UpdateDelayedWorkQueue and ready delayed tasks are moved into the
// |delayed_work_queue|. Note the EnqueueOrder (used for ordering) for a delayed
// task is not set until it's moved into the |delayed_work_queue|.
//
// TaskQueueImpl uses the WorkQueueSets and the TaskQueueSelector to implement
// prioritization. Task selection is done by the TaskQueueSelector and when a
// queue is selected, it round-robins between the |immediate_work_queue| and
// |delayed_work_queue|. The reason for this is we want to make sure delayed
// tasks (normally the most common type) don't starve out immediate work.
class BASE_EXPORT TaskQueueImpl : public TaskQueue {
public:
// Initializes the state of all the task queue features. Must be invoked
// after FeatureList initialization and while Chrome is still single-threaded.
static void InitializeFeatures();
TaskQueueImpl(SequenceManagerImpl* sequence_manager,
WakeUpQueue* wake_up_queue,
const TaskQueue::Spec& spec);
TaskQueueImpl(const TaskQueueImpl&) = delete;
TaskQueueImpl& operator=(const TaskQueueImpl&) = delete;
~TaskQueueImpl() override;
// Types of queues TaskQueueImpl is maintaining internally.
enum class WorkQueueType { kImmediate, kDelayed };
// Some methods have fast paths when on the main thread.
enum class CurrentThread { kMainThread, kNotMainThread };
// Non-nestable tasks may get deferred but such queue is being maintained on
// SequenceManager side, so we need to keep information how to requeue it.
struct DeferredNonNestableTask {
Task task;
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of sampling
// profiler data and tab_search:top100:2020).
RAW_PTR_EXCLUSION internal::TaskQueueImpl* task_queue;
WorkQueueType work_queue_type;
};
using OnNextWakeUpChangedCallback = RepeatingCallback<void(TimeTicks)>;
using OnTaskStartedHandler =
RepeatingCallback<void(const Task&, const TaskQueue::TaskTiming&)>;
using OnTaskCompletedHandler =
RepeatingCallback<void(const Task&, TaskQueue::TaskTiming*, LazyNow*)>;
using OnTaskPostedHandler = RepeatingCallback<void(const Task&)>;
using TaskExecutionTraceLogger =
RepeatingCallback<void(perfetto::EventContext&, const Task&)>;
// TaskQueue implementation.
const char* GetName() const override;
bool IsQueueEnabled() const override;
bool IsEmpty() const override;
size_t GetNumberOfPendingTasks() const override;
bool HasTaskToRunImmediatelyOrReadyDelayedTask() const override;
std::optional<WakeUp> GetNextDesiredWakeUp() override;
void SetQueuePriority(TaskQueue::QueuePriority priority) override;
TaskQueue::QueuePriority GetQueuePriority() const override;
void AddTaskObserver(TaskObserver* task_observer) override;
void RemoveTaskObserver(TaskObserver* task_observer) override;
void InsertFence(TaskQueue::InsertFencePosition position) override;
void InsertFenceAt(TimeTicks time) override;
void RemoveFence() override;
bool HasActiveFence() override;
bool BlockedByFence() const override;
void SetThrottler(TaskQueue::Throttler* throttler) override;
void ResetThrottler() override;
void UpdateWakeUp(LazyNow* lazy_now) override;
void SetShouldReportPostedTasksWhenDisabled(bool should_report) override;
scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner(
TaskType task_type) const override;
const scoped_refptr<SingleThreadTaskRunner>& task_runner() const override;
void SetOnTaskStartedHandler(OnTaskStartedHandler handler) override;
void SetOnTaskCompletedHandler(OnTaskCompletedHandler handler) override;
[[nodiscard]] std::unique_ptr<TaskQueue::OnTaskPostedCallbackHandle>
AddOnTaskPostedHandler(OnTaskPostedHandler handler) override;
void SetTaskExecutionTraceLogger(TaskExecutionTraceLogger logger) override;
std::unique_ptr<QueueEnabledVoter> CreateQueueEnabledVoter() override;
void SetQueueEnabled(bool enabled);
void UnregisterTaskQueue();
QueueName GetProtoName() const;
// Returns true if a (potentially hypothetical) task with the specified
// |enqueue_order| could run on the queue. Must be called from the main
// thread.
bool CouldTaskRun(EnqueueOrder enqueue_order) const;
// Returns true if a task with |enqueue_order| obtained from this queue was
// ever in the queue while it was disabled, blocked by a fence, or less
// important than kNormalPriority.
bool WasBlockedOrLowPriority(EnqueueOrder enqueue_order) const;
// Must only be called from the thread this task queue was created on.
void ReloadEmptyImmediateWorkQueue();
Value::Dict AsValue(TimeTicks now, bool force_verbose) const;
bool GetQuiescenceMonitored() const { return should_monitor_quiescence_; }
bool GetShouldNotifyObservers() const { return should_notify_observers_; }
void NotifyWillProcessTask(const Task& task,
bool was_blocked_or_low_priority);
void NotifyDidProcessTask(const Task& task);
// Returns true iff this queue has work that can execute now, i.e. immediate
// tasks or delayed tasks that have been transferred to the work queue by
// MoveReadyDelayedTasksToWorkQueue(). Delayed tasks that are still in the
// incoming queue are not taken into account. Ignores the queue's enabled
// state and fences.
bool HasTaskToRunImmediately() const;
bool HasTaskToRunImmediatelyLocked() const
EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
bool has_pending_high_resolution_tasks() const {
return main_thread_only()
.delayed_incoming_queue.has_pending_high_resolution_tasks();
}
WorkQueue* delayed_work_queue() {
return main_thread_only().delayed_work_queue.get();
}
const WorkQueue* delayed_work_queue() const {
return main_thread_only().delayed_work_queue.get();
}
WorkQueue* immediate_work_queue() {
return main_thread_only().immediate_work_queue.get();
}
const WorkQueue* immediate_work_queue() const {
return main_thread_only().immediate_work_queue.get();
}
TaskExecutionTraceLogger task_execution_trace_logger() const {
return main_thread_only().task_execution_trace_logger;
}
// Removes all canceled tasks from the front of the delayed incoming queue.
// After calling this, GetNextDesiredWakeUp() is guaranteed to return a time
// for a non-canceled task, if one exists. Return true if a canceled task was
// removed.
bool RemoveAllCanceledDelayedTasksFromFront(LazyNow* lazy_now);
// Enqueues in `delayed_work_queue` all delayed tasks which must run now
// (cannot be postponed) and possibly some delayed tasks which can run now but
// could be postponed (due to how tasks are stored, it is not possible to
// retrieve all such tasks efficiently). Must be called from the main thread.
void MoveReadyDelayedTasksToWorkQueue(LazyNow* lazy_now,
EnqueueOrder enqueue_order);
void OnWakeUp(LazyNow* lazy_now, EnqueueOrder enqueue_order);
const WakeUpQueue* wake_up_queue() const {
return main_thread_only().wake_up_queue;
}
HeapHandle heap_handle() const { return main_thread_only().heap_handle; }
void set_heap_handle(HeapHandle heap_handle) {
main_thread_only().heap_handle = heap_handle;
}
// Pushes |task| onto the front of the specified work queue. Caution must be
// taken with this API because you could easily starve out other work.
// TODO(kraynov): Simplify non-nestable task logic https://crbug.com/845437.
void RequeueDeferredNonNestableTask(DeferredNonNestableTask task);
void PushImmediateIncomingTaskForTest(Task task);
// Iterates over |delayed_incoming_queue| removing canceled tasks. In
// addition MaybeShrinkQueue is called on all internal queues.
void ReclaimMemory(TimeTicks now);
void OnTaskStarted(const Task& task,
const TaskQueue::TaskTiming& task_timing);
void OnTaskCompleted(const Task& task,
TaskQueue::TaskTiming* task_timing,
LazyNow* lazy_now);
bool RequiresTaskTiming() const;
WeakPtr<SequenceManagerImpl> GetSequenceManagerWeakPtr();
SequenceManagerImpl* sequence_manager() const { return sequence_manager_; }
// Returns true if this queue is unregistered or task queue manager is deleted
// and this queue can be safely deleted on any thread.
bool IsUnregistered() const;
// Called by the associated sequence manager when it becomes bound. Updates
// the weak pointer stored in voters with one bound to the correct thread.
void CompleteInitializationOnBoundThread();
void AddQueueEnabledVoter(bool voter_is_enabled,
TaskQueue::QueueEnabledVoter& voter);
void RemoveQueueEnabledVoter(bool voter_is_enabled,
TaskQueue::QueueEnabledVoter& voter);
void OnQueueEnabledVoteChanged(bool enabled);
protected:
// Sets this queue's next wake up time to |wake_up| in the time domain.
void SetNextWakeUp(LazyNow* lazy_now, std::optional<WakeUp> wake_up);
private:
friend class WorkQueue;
friend class WorkQueueTest;
friend class DelayedTaskHandleDelegate;
// A TaskQueueImpl instance can be destroyed or unregistered before all its
// associated TaskRunner instances are (they are refcounted). Thus we need a
// way to prevent TaskRunner instances from posting further tasks. This class
// guards PostTask calls using an OperationsController.
// This class is ref-counted as both the TaskQueueImpl instance and all
// associated TaskRunner instances share the same GuardedTaskPoster instance.
// When TaskQueueImpl shuts down it calls ShutdownAndWaitForZeroOperations(),
// preventing further PostTask calls being made to the underlying
// TaskQueueImpl.
class GuardedTaskPoster : public RefCountedThreadSafe<GuardedTaskPoster> {
public:
explicit GuardedTaskPoster(TaskQueueImpl* outer);
bool PostTask(PostedTask task);
DelayedTaskHandle PostCancelableTask(PostedTask task);
bool RunOrPostTask(PostedTask task);
void StartAcceptingOperations() {
operations_controller_.StartAcceptingOperations();
}
void ShutdownAndWaitForZeroOperations() {
operations_controller_.ShutdownAndWaitForZeroOperations();
// `operations_controller_` won't let any more operations here, and
// `outer_` might get destroyed before `this` does, so clearing `outer_`
// avoids a potential dangling pointer.
outer_ = nullptr;
}
private:
friend class RefCountedThreadSafe<GuardedTaskPoster>;
~GuardedTaskPoster();
base::internal::OperationsController operations_controller_;
// Pointer might be stale, access guarded by |operations_controller_|
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of
// speedometer3).
RAW_PTR_EXCLUSION TaskQueueImpl* outer_ = nullptr;
};
class TaskRunner final : public SingleThreadTaskRunner {
public:
explicit TaskRunner(scoped_refptr<GuardedTaskPoster> task_poster,
scoped_refptr<AssociatedThreadId> associated_thread,
TaskType task_type);
bool PostDelayedTask(const Location& location,
OnceClosure callback,
TimeDelta delay) final;
bool PostDelayedTaskAt(subtle::PostDelayedTaskPassKey,
const Location& location,
OnceClosure callback,
TimeTicks delayed_run_time,
base::subtle::DelayPolicy delay_policy) final;
DelayedTaskHandle PostCancelableDelayedTaskAt(
subtle::PostDelayedTaskPassKey,
const Location& location,
OnceClosure callback,
TimeTicks delayed_run_time,
base::subtle::DelayPolicy delay_policy) final;
DelayedTaskHandle PostCancelableDelayedTask(subtle::PostDelayedTaskPassKey,
const Location& location,
OnceClosure callback,
TimeDelta delay) final;
bool PostNonNestableDelayedTask(const Location& location,
OnceClosure callback,
TimeDelta delay) final;
bool RunOrPostTask(subtle::RunOrPostTaskPassKey,
const Location& from_here,
OnceClosure task) final;
bool BelongsToCurrentThread() const final;
bool RunsTasksInCurrentSequence() const final;
private:
~TaskRunner() final;
const scoped_refptr<GuardedTaskPoster> task_poster_;
const scoped_refptr<AssociatedThreadId> associated_thread_;
const TaskType task_type_;
};
class OnTaskPostedCallbackHandleImpl
: public TaskQueue::OnTaskPostedCallbackHandle {
public:
OnTaskPostedCallbackHandleImpl(
TaskQueueImpl* task_queue_impl,
scoped_refptr<const AssociatedThreadId> associated_thread_);
~OnTaskPostedCallbackHandleImpl() override;
// Callback handles can outlive the associated TaskQueueImpl, so the
// reference needs to be cleared when the queue is unregistered.
void UnregisterTaskQueue() { task_queue_impl_ = nullptr; }
private:
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of
// speedometer3).
RAW_PTR_EXCLUSION TaskQueueImpl* task_queue_impl_ = nullptr;
const scoped_refptr<const AssociatedThreadId> associated_thread_;
};
// A queue for holding delayed tasks before their delay has expired.
struct DelayedIncomingQueue {
public:
DelayedIncomingQueue();
DelayedIncomingQueue(const DelayedIncomingQueue&) = delete;
DelayedIncomingQueue& operator=(const DelayedIncomingQueue&) = delete;
~DelayedIncomingQueue();
void push(Task task);
void remove(HeapHandle heap_handle);
Task take_top();
bool empty() const { return queue_.empty(); }
size_t size() const { return queue_.size(); }
const Task& top() const LIFETIME_BOUND { return queue_.top(); }
void swap(DelayedIncomingQueue* other);
bool has_pending_high_resolution_tasks() const {
return pending_high_res_tasks_;
}
// TODO(crbug.com/40735653): we pass SequenceManager to be able to record
// crash keys. Remove this parameter after chasing down this crash.
void SweepCancelledTasks(SequenceManagerImpl* sequence_manager);
Value::List AsValue(TimeTicks now) const;
private:
struct Compare {
bool operator()(const Task& lhs, const Task& rhs) const;
};
IntrusiveHeap<Task, Compare> queue_;
// Number of pending tasks in the queue that need high resolution timing.
int pending_high_res_tasks_ = 0;
};
struct MainThreadOnly {
MainThreadOnly(TaskQueueImpl* task_queue, WakeUpQueue* wake_up_queue);
~MainThreadOnly();
raw_ptr<WakeUpQueue> wake_up_queue;
raw_ptr<TaskQueue::Throttler> throttler = nullptr;
std::unique_ptr<WorkQueue> delayed_work_queue;
std::unique_ptr<WorkQueue> immediate_work_queue;
DelayedIncomingQueue delayed_incoming_queue;
ObserverList<TaskObserver>::UncheckedAndDanglingUntriaged task_observers;
HeapHandle heap_handle;
bool is_enabled = true;
std::optional<Fence> current_fence;
std::optional<TimeTicks> delayed_fence;
// Snapshots the next sequence number when the queue is unblocked, otherwise
// it contains EnqueueOrder::none(). If the EnqueueOrder of a task just
// popped from this queue is greater than this, it means that the queue was
// never disabled or blocked by a fence while the task was queued.
EnqueueOrder enqueue_order_at_which_we_became_unblocked;
// If the EnqueueOrder of a task just popped from this queue is greater than
// this, it means that the queue was never disabled, blocked by a fence or
// less important than kNormalPriority while the task was queued.
//
// Implementation details:
// 1) When the queue is made less important than kNormalPriority, this is
// set to EnqueueOrder::max(). The EnqueueOrder of any task will compare
// less than this.
// 2) When the queue is made at least as important as kNormalPriority, this
// snapshots the next sequence number. If the queue is blocked, the value
// is irrelevant because no task should be popped. If the queue is not
// blocked, the EnqueueOrder of any already queued task will compare less
// than this.
// 3) When the queue is unblocked while at least as important as
// kNormalPriority, this snapshots the next sequence number. The
// EnqueueOrder of any already queued task will compare less than this.
//
// TODO(crbug.com/40791504): Change this to use `TaskOrder`.
EnqueueOrder
enqueue_order_at_which_we_became_unblocked_with_normal_priority;
OnTaskStartedHandler on_task_started_handler;
OnTaskCompletedHandler on_task_completed_handler;
TaskExecutionTraceLogger task_execution_trace_logger;
// Last reported wake up, used only in UpdateWakeUp to avoid
// excessive calls.
std::optional<WakeUp> scheduled_wake_up;
// If false, queue will be disabled. Used only for tests.
bool is_enabled_for_test = true;
// The time at which the task queue was disabled, if it is currently
// disabled.
std::optional<TimeTicks> disabled_time;
// Whether or not the task queue should emit tracing events for tasks
// posted to this queue when it is disabled.
bool should_report_posted_tasks_when_disabled = false;
int enabled_voter_count = 0;
int voter_count = 0;
};
void PostTask(PostedTask task);
void RemoveCancelableTask(HeapHandle heap_handle);
void PostImmediateTaskImpl(PostedTask task, CurrentThread current_thread);
void PostDelayedTaskImpl(PostedTask task, CurrentThread current_thread);
// Push the task onto the |delayed_incoming_queue|. Lock-free main thread
// only fast path.
void PushOntoDelayedIncomingQueueFromMainThread(Task pending_task,
LazyNow* lazy_now,
bool notify_task_annotator);
// Push the task onto the |delayed_incoming_queue|. Slow path from other
// threads.
void PushOntoDelayedIncomingQueue(Task pending_task);
void ScheduleDelayedWorkTask(Task pending_task);
void MoveReadyImmediateTasksToImmediateWorkQueueLocked()
EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
// LazilyDeallocatedDeque use TimeTicks to figure out when to resize. We
// should use real time here always.
using TaskDeque =
LazilyDeallocatedDeque<Task, subtle::TimeTicksNowIgnoringOverride>;
// Extracts all the tasks from the immediate incoming queue and swaps it with
// |queue| which must be empty.
// Can be called from any thread.
void TakeImmediateIncomingQueueTasks(TaskDeque* queue);
void TraceQueueSize() const;
static Value::List QueueAsValue(const TaskDeque& queue, TimeTicks now);
static Value::Dict TaskAsValue(const Task& task, TimeTicks now);
// Returns a Task representation for `delayed_task`.
Task MakeDelayedTask(PostedTask delayed_task, LazyNow* lazy_now) const;
// Activate a delayed fence if a time has come based on `task`'s delayed run
// time.
void ActivateDelayedFenceIfNeeded(const Task& task);
// Updates state protected by any_thread_lock_.
void UpdateCrossThreadQueueStateLocked()
EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
TimeDelta GetTaskDelayAdjustment(CurrentThread current_thread);
// Reports the task if it was due to IPC and was posted to a disabled queue.
// This should be called after WillQueueTask has been called for the task.
void MaybeReportIpcTaskQueuedFromMainThread(const Task& pending_task);
bool ShouldReportIpcTaskQueuedFromAnyThreadLocked(
base::TimeDelta* time_since_disabled)
EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
void MaybeReportIpcTaskQueuedFromAnyThreadLocked(const Task& pending_task)
EXCLUSIVE_LOCKS_REQUIRED(any_thread_lock_);
void MaybeReportIpcTaskQueuedFromAnyThreadUnlocked(const Task& pending_task);
void ReportIpcTaskQueued(const Task& pending_task,
const base::TimeDelta& time_since_disabled);
// Invoked when the queue becomes enabled and not blocked by a fence.
void OnQueueUnblocked();
void InsertFence(Fence fence);
void RemoveOnTaskPostedHandler(
OnTaskPostedCallbackHandleImpl* on_task_posted_callback_handle);
TaskQueue::QueuePriority DefaultPriority() const;
bool AreAllQueueEnabledVotersEnabled() const {
return main_thread_only().enabled_voter_count ==
main_thread_only().voter_count;
}
// Returns whether the queue is enabled. May be invoked from any thread.
bool IsQueueEnabledFromAnyThread() const;
QueueName name_;
const raw_ptr<SequenceManagerImpl, AcrossTasksDanglingUntriaged>
sequence_manager_;
const scoped_refptr<AssociatedThreadId> associated_thread_;
const scoped_refptr<GuardedTaskPoster> task_poster_;
mutable base::internal::CheckedLock any_thread_lock_;
struct AnyThread {
// Mirrored from MainThreadOnly. These are only used for tracing.
struct TracingOnly {
TracingOnly();
~TracingOnly();
std::optional<TimeTicks> disabled_time;
bool should_report_posted_tasks_when_disabled = false;
};
AnyThread();
~AnyThread();
TaskDeque immediate_incoming_queue;
bool immediate_work_queue_empty = true;
bool post_immediate_task_should_schedule_work = true;
bool unregistered = false;
bool is_enabled = true;
base::flat_map<raw_ptr<OnTaskPostedCallbackHandleImpl>, OnTaskPostedHandler>
on_task_posted_handlers;
#if DCHECK_IS_ON()
// A cache of |immediate_work_queue->work_queue_set_index()| which is used
// to index into
// SequenceManager::Settings::per_priority_cross_thread_task_delay to apply
// a priority specific delay for debugging purposes.
size_t queue_set_index = 0;
#endif
TracingOnly tracing_only;
};
AnyThread any_thread_ GUARDED_BY(any_thread_lock_);
MainThreadOnly main_thread_only_;
MainThreadOnly& main_thread_only() {
associated_thread_->AssertInSequenceWithCurrentThread();
return main_thread_only_;
}
const MainThreadOnly& main_thread_only() const LIFETIME_BOUND {
associated_thread_->AssertInSequenceWithCurrentThread();
return main_thread_only_;
}
// Handle to our entry within the SequenceManagers |empty_queues_to_reload_|
// atomic flag set. Used to signal that this queue needs to be reloaded.
// If you call SetActive(false) you should do so inside |any_thread_lock_|
// because there is a danger a cross thread PostTask might reset it before we
// make |immediate_work_queue| non-empty.
AtomicFlagSet::AtomicFlag empty_queues_to_reload_handle_;
const bool should_monitor_quiescence_;
const bool should_notify_observers_;
const bool delayed_fence_allowed_;
const scoped_refptr<SingleThreadTaskRunner> default_task_runner_;
base::WeakPtrFactory<TaskQueueImpl> voter_weak_ptr_factory_{this};
};
} // namespace sequence_manager::internal
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_IMPL_H_

View File

@@ -0,0 +1,287 @@
// Copyright 2014 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/task_queue_selector.h"
#include <bit>
#include <optional>
#include <utility>
#include "base/check_op.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/task/task_features.h"
#include "base/threading/thread_checker.h"
#include "base/trace_event/base_tracing.h"
namespace base {
namespace sequence_manager {
namespace internal {
TaskQueueSelector::TaskQueueSelector(
scoped_refptr<const AssociatedThreadId> associated_thread,
const SequenceManager::Settings& settings)
: associated_thread_(std::move(associated_thread)),
#if DCHECK_IS_ON()
random_task_selection_(settings.random_task_selection_seed != 0),
#endif
non_empty_set_counts_(
std::vector<int>(settings.priority_settings.priority_count(), 0)),
delayed_work_queue_sets_("delayed", this, settings),
immediate_work_queue_sets_("immediate", this, settings) {
}
TaskQueueSelector::~TaskQueueSelector() = default;
void TaskQueueSelector::AddQueue(internal::TaskQueueImpl* queue,
TaskQueue::QueuePriority priority) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK(queue->IsQueueEnabled());
AddQueueImpl(queue, priority);
}
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, priority_count());
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());
}
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
}
void TaskQueueSelector::WorkQueueSetBecameEmpty(size_t set_index) {
non_empty_set_counts_[set_index]--;
DCHECK_GE(non_empty_set_counts_[set_index], 0);
// There are no delayed or immediate tasks for |set_index| so remove from
// |active_priority_tracker_|.
if (non_empty_set_counts_[set_index] == 0) {
active_priority_tracker_.SetActive(
static_cast<TaskQueue::QueuePriority>(set_index), false);
}
}
void TaskQueueSelector::WorkQueueSetBecameNonEmpty(size_t set_index) {
non_empty_set_counts_[set_index]++;
DCHECK_LE(non_empty_set_counts_[set_index], kMaxNonEmptySetCount);
// There is now a delayed or an immediate task for |set_index|, so add to
// |active_priority_tracker_|.
if (non_empty_set_counts_[set_index] == 1) {
bool had_active_priority = active_priority_tracker_.HasActivePriority();
TaskQueue::QueuePriority priority =
static_cast<TaskQueue::QueuePriority>(set_index);
active_priority_tracker_.SetActive(priority, true);
if (!had_active_priority && task_queue_selector_observer_) {
task_queue_selector_observer_->OnWorkAvailable();
}
}
}
void TaskQueueSelector::CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const {
delayed_work_queue_sets_.CollectSkippedOverLowerPriorityTasks(
selected_work_queue, result);
immediate_work_queue_sets_.CollectSkippedOverLowerPriorityTasks(
selected_work_queue, result);
}
#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(
SelectTaskOption option) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
auto highest_priority = GetHighestPendingPriority(option);
if (!highest_priority.has_value())
return nullptr;
// Select the priority from which we will select a task. Usually this is
// the highest priority for which we have work, unless we are starving a lower
// priority.
TaskQueue::QueuePriority priority = highest_priority.value();
// For selecting an immediate queue only, the highest priority can be used as
// a starting priority, but it is required to check work at other priorities.
// For the case where a delayed task is at a higher priority than an immediate
// task, HighestActivePriority(...) returns the priority of the delayed task
// but the resulting queue must be the lower one.
if (option == SelectTaskOption::kSkipDelayedTask) {
WorkQueue* queue =
#if DCHECK_IS_ON()
random_task_selection_
? ChooseImmediateOnlyWithPriority<SetOperationRandom>(priority)
:
#endif
ChooseImmediateOnlyWithPriority<SetOperationOldest>(priority);
return queue;
}
WorkQueue* queue =
#if DCHECK_IS_ON()
random_task_selection_ ? ChooseWithPriority<SetOperationRandom>(priority)
:
#endif
ChooseWithPriority<SetOperationOldest>(priority);
// If we have selected a delayed task while having an immediate task of the
// same priority, increase the starvation count.
if (queue->queue_type() == WorkQueue::QueueType::kDelayed &&
!immediate_work_queue_sets_.IsSetEmpty(priority)) {
immediate_starvation_count_++;
} else {
immediate_starvation_count_ = 0;
}
return queue;
}
Value::Dict TaskQueueSelector::AsValue() const {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
Value::Dict state;
state.Set("immediate_starvation_count", immediate_starvation_count_);
return state;
}
void TaskQueueSelector::SetTaskQueueSelectorObserver(Observer* observer) {
task_queue_selector_observer_ = observer;
}
std::optional<TaskQueue::QueuePriority>
TaskQueueSelector::GetHighestPendingPriority(SelectTaskOption option) const {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
if (!active_priority_tracker_.HasActivePriority())
return std::nullopt;
TaskQueue::QueuePriority highest_priority =
active_priority_tracker_.HighestActivePriority();
DCHECK_LT(highest_priority, priority_count());
if (option != SelectTaskOption::kSkipDelayedTask)
return highest_priority;
for (; highest_priority != priority_count(); ++highest_priority) {
if (active_priority_tracker_.IsActive(highest_priority) &&
!immediate_work_queue_sets_.IsSetEmpty(highest_priority)) {
return highest_priority;
}
}
return std::nullopt;
}
void TaskQueueSelector::SetImmediateStarvationCountForTest(
int immediate_starvation_count) {
immediate_starvation_count_ = immediate_starvation_count;
}
bool TaskQueueSelector::HasTasksWithPriority(
TaskQueue::QueuePriority priority) const {
return !delayed_work_queue_sets_.IsSetEmpty(priority) ||
!immediate_work_queue_sets_.IsSetEmpty(priority);
}
TaskQueueSelector::ActivePriorityTracker::ActivePriorityTracker() = default;
void TaskQueueSelector::ActivePriorityTracker::SetActive(
TaskQueue::QueuePriority priority,
bool is_active) {
DCHECK_LT(priority, SequenceManager::PrioritySettings::kMaxPriorities);
DCHECK_NE(IsActive(priority), is_active);
if (is_active) {
active_priorities_ |= (size_t{1} << static_cast<size_t>(priority));
} else {
active_priorities_ &= ~(size_t{1} << static_cast<size_t>(priority));
}
}
TaskQueue::QueuePriority
TaskQueueSelector::ActivePriorityTracker::HighestActivePriority() const {
DCHECK_NE(active_priorities_, 0u);
return static_cast<TaskQueue::QueuePriority>(
std::countr_zero(active_priorities_));
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,266 @@
// 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.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
#include <stddef.h>
#include <atomic>
#include <optional>
#include <vector>
#include "base/base_export.h"
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "base/pending_task.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/sequenced_task_source.h"
#include "base/task/sequence_manager/task_order.h"
#include "base/task/sequence_manager/work_queue_sets.h"
#include "base/values.h"
namespace base {
namespace sequence_manager {
namespace internal {
class AssociatedThreadId;
// TaskQueueSelector is used by the SchedulerHelper to enable prioritization
// of particular task queues.
class BASE_EXPORT TaskQueueSelector : public WorkQueueSets::Observer {
public:
using SelectTaskOption = SequencedTaskSource::SelectTaskOption;
TaskQueueSelector(scoped_refptr<const AssociatedThreadId> associated_thread,
const SequenceManager::Settings& settings);
TaskQueueSelector(const TaskQueueSelector&) = delete;
TaskQueueSelector& operator=(const TaskQueueSelector&) = delete;
~TaskQueueSelector() override;
// Called to register a queue that can be selected. This function is called
// on the main thread.
void AddQueue(internal::TaskQueueImpl* queue,
TaskQueue::QueuePriority priority);
// The specified work will no longer be considered for selection. This
// function is called on the main thread.
void RemoveQueue(internal::TaskQueueImpl* queue);
// Make |queue| eligible for selection. This function is called on the main
// thread. Must only be called if |queue| is disabled.
void EnableQueue(internal::TaskQueueImpl* queue);
// Disable selection from |queue|. Must only be called if |queue| is enabled.
void DisableQueue(internal::TaskQueueImpl* queue);
// Called get or set the priority of |queue|.
void SetQueuePriority(internal::TaskQueueImpl* queue,
TaskQueue::QueuePriority priority);
// Called to choose the work queue from which the next task should be taken
// and run. Return the queue to service if there is one or null otherwise.
// This function is called on the main thread.
WorkQueue* SelectWorkQueueToService(
SelectTaskOption option = SelectTaskOption::kDefault);
// Serialize the selector state for tracing/debugging.
Value::Dict AsValue() const;
class BASE_EXPORT Observer {
public:
virtual ~Observer() = default;
// Called when |queue| transitions from disabled to enabled.
virtual void OnTaskQueueEnabled(internal::TaskQueueImpl* queue) = 0;
// Called when work becomes available.
virtual void OnWorkAvailable() = 0;
};
// Called once to set the Observer. This function is called
// on the main thread. If |observer| is null, then no callbacks will occur.
void SetTaskQueueSelectorObserver(Observer* observer);
// Returns the priority of the most important pending task if one exists.
// O(1).
std::optional<TaskQueue::QueuePriority> GetHighestPendingPriority(
SelectTaskOption option = SelectTaskOption::kDefault) const;
// WorkQueueSets::Observer implementation:
void WorkQueueSetBecameEmpty(size_t set_index) override;
void WorkQueueSetBecameNonEmpty(size_t set_index) override;
// Populates |result| with tasks with lower priority than the first task from
// |selected_work_queue| which could otherwise run now.
void CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const;
protected:
WorkQueueSets* delayed_work_queue_sets() { return &delayed_work_queue_sets_; }
WorkQueueSets* immediate_work_queue_sets() {
return &immediate_work_queue_sets_;
}
// This method will force select an immediate task if those are being
// starved by delayed tasks.
void SetImmediateStarvationCountForTest(int immediate_starvation_count);
// Tracks which priorities are currently active, meaning there are pending
// runnable tasks with that priority. Because there are only a handful of
// priorities, and because we always run tasks in order from highest to lowest
// priority, we can use a single integer to represent enabled priorities,
// using a bit per priority.
class BASE_EXPORT ActivePriorityTracker {
public:
ActivePriorityTracker();
bool HasActivePriority() const { return active_priorities_ != 0; }
bool IsActive(TaskQueue::QueuePriority priority) const {
return active_priorities_ & (size_t{1} << static_cast<size_t>(priority));
}
void SetActive(TaskQueue::QueuePriority priority, bool is_active);
TaskQueue::QueuePriority HighestActivePriority() const;
private:
static_assert(SequenceManager::PrioritySettings::kMaxPriorities <
sizeof(size_t) * 8,
"The number of priorities must be strictly less than the "
"number of bits of |active_priorities_|!");
size_t active_priorities_ = 0;
};
/*
* SetOperation is used to configure ChooseWithPriority() and must have:
*
* static std::optional<WorkQueueAndTaskOrder>
* GetWithPriority(const WorkQueueSets& sets,
* TaskQueue::QueuePriority priority);
*/
// The default
struct SetOperationOldest {
static std::optional<WorkQueueAndTaskOrder> GetWithPriority(
const WorkQueueSets& sets,
TaskQueue::QueuePriority priority) {
return sets.GetOldestQueueAndTaskOrderInSet(priority);
}
};
#if DCHECK_IS_ON()
struct SetOperationRandom {
static std::optional<WorkQueueAndTaskOrder> GetWithPriority(
const WorkQueueSets& sets,
TaskQueue::QueuePriority priority) {
return sets.GetRandomQueueAndTaskOrderInSet(priority);
}
};
#endif // DCHECK_IS_ON()
template <typename SetOperation>
WorkQueue* ChooseWithPriority(TaskQueue::QueuePriority priority) const {
// Maximum number of delayed tasks tasks which can be run while there's a
// waiting non-delayed task.
static const int kMaxDelayedStarvationTasks = 3;
// Select an immediate work queue if we are starving immediate tasks.
if (immediate_starvation_count_ >= kMaxDelayedStarvationTasks) {
WorkQueue* queue =
ChooseImmediateOnlyWithPriority<SetOperation>(priority);
if (queue)
return queue;
return ChooseDelayedOnlyWithPriority<SetOperation>(priority);
}
return ChooseImmediateOrDelayedTaskWithPriority<SetOperation>(priority);
}
template <typename SetOperation>
WorkQueue* ChooseImmediateOnlyWithPriority(
TaskQueue::QueuePriority priority) const {
if (auto queue_and_order = SetOperation::GetWithPriority(
immediate_work_queue_sets_, priority)) {
return queue_and_order->queue;
}
return nullptr;
}
template <typename SetOperation>
WorkQueue* ChooseDelayedOnlyWithPriority(
TaskQueue::QueuePriority priority) const {
if (auto queue_and_order =
SetOperation::GetWithPriority(delayed_work_queue_sets_, priority)) {
return queue_and_order->queue;
}
return nullptr;
}
private:
size_t priority_count() const { return non_empty_set_counts_.size(); }
void ChangeSetIndex(internal::TaskQueueImpl* queue,
TaskQueue::QueuePriority priority);
void AddQueueImpl(internal::TaskQueueImpl* queue,
TaskQueue::QueuePriority priority);
void RemoveQueueImpl(internal::TaskQueueImpl* queue);
#if DCHECK_IS_ON() || !defined(NDEBUG)
bool CheckContainsQueueForTest(const internal::TaskQueueImpl* queue) const;
#endif
template <typename SetOperation>
WorkQueue* ChooseImmediateOrDelayedTaskWithPriority(
TaskQueue::QueuePriority priority) const {
if (auto immediate_queue_and_order = SetOperation::GetWithPriority(
immediate_work_queue_sets_, priority)) {
if (auto delayed_queue_and_order = SetOperation::GetWithPriority(
delayed_work_queue_sets_, priority)) {
return immediate_queue_and_order->order < delayed_queue_and_order->order
? immediate_queue_and_order->queue
: delayed_queue_and_order->queue;
}
return immediate_queue_and_order->queue;
}
return ChooseDelayedOnlyWithPriority<SetOperation>(priority);
}
// Returns true if there are pending tasks with priority |priority|.
bool HasTasksWithPriority(TaskQueue::QueuePriority priority) const;
const scoped_refptr<const AssociatedThreadId> associated_thread_;
#if DCHECK_IS_ON()
const bool random_task_selection_ = false;
#endif
// Count of the number of sets (delayed or immediate) for each priority.
// Should only contain 0, 1 or 2.
std::vector<int> non_empty_set_counts_;
static constexpr const int kMaxNonEmptySetCount = 2;
// An atomic is used here because InitializeFeatures() can race with
// SequenceManager reading this.
static std::atomic_int g_max_delayed_starvation_tasks;
// List of active priorities, which is used to work out which priority to run
// next.
ActivePriorityTracker active_priority_tracker_;
WorkQueueSets delayed_work_queue_sets_;
WorkQueueSets immediate_work_queue_sets_;
int immediate_starvation_count_ = 0;
raw_ptr<Observer> task_queue_selector_observer_ = nullptr; // Not owned.
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_

View File

@@ -0,0 +1,13 @@
// Copyright 2024 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/task_time_observer.h"
namespace base::sequence_manager {
TaskTimeObserver::~TaskTimeObserver() {
CHECK(!IsInObserverList());
}
} // namespace base::sequence_manager

View File

@@ -0,0 +1,33 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_TIME_OBSERVER_H_
#define BASE_TASK_SEQUENCE_MANAGER_TASK_TIME_OBSERVER_H_
#include "base/base_export.h"
#include "base/observer_list_types.h"
#include "base/time/time.h"
namespace base {
namespace sequence_manager {
// TaskTimeObserver provides an API for observing completion of tasks.
class BASE_EXPORT TaskTimeObserver : public CheckedObserver {
public:
TaskTimeObserver() = default;
TaskTimeObserver(const TaskTimeObserver&) = delete;
TaskTimeObserver& operator=(const TaskTimeObserver&) = delete;
~TaskTimeObserver() override;
// To be called when task is about to start.
virtual void WillProcessTask(TimeTicks start_time) = 0;
// To be called when task is completed.
virtual void DidProcessTask(TimeTicks start_time, TimeTicks end_time) = 0;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_TIME_OBSERVER_H_

View File

@@ -0,0 +1,150 @@
// Copyright 2018 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/tasks.h"
#include "base/task/sequence_manager/task_order.h"
namespace base {
namespace sequence_manager {
Task::Task(internal::PostedTask posted_task,
EnqueueOrder sequence_order,
EnqueueOrder enqueue_order,
TimeTicks queue_time,
WakeUpResolution resolution,
TimeDelta leeway)
: PendingTask(posted_task.location,
std::move(posted_task.callback),
queue_time,
absl::holds_alternative<base::TimeTicks>(
posted_task.delay_or_delayed_run_time)
? absl::get<base::TimeTicks>(
posted_task.delay_or_delayed_run_time)
: base::TimeTicks(),
leeway,
posted_task.delay_policy),
nestable(posted_task.nestable),
task_type(posted_task.task_type),
task_runner(std::move(posted_task.task_runner)),
enqueue_order_(enqueue_order),
delayed_task_handle_delegate_(
std::move(posted_task.delayed_task_handle_delegate)) {
DCHECK(!absl::holds_alternative<base::TimeDelta>(
posted_task.delay_or_delayed_run_time) ||
absl::get<base::TimeDelta>(posted_task.delay_or_delayed_run_time)
.is_zero());
// We use |sequence_num| when comparing PendingTask for ordering purposes
// and it may wrap around to a negative number during the static cast, hence,
// TaskQueueImpl::DelayedIncomingQueue is especially sensitive to a potential
// change of |PendingTask::sequence_num|'s type.
static_assert(std::is_same_v<decltype(sequence_num), int>, "");
sequence_num = static_cast<int>(sequence_order);
this->is_high_res = resolution == WakeUpResolution::kHigh;
}
Task::Task(Task&& move_from) = default;
Task::~Task() = default;
Task& Task::operator=(Task&& other) = default;
TaskOrder Task::task_order() const {
return TaskOrder(
enqueue_order(),
delayed_run_time.is_null() ? TimeTicks() : latest_delayed_run_time(),
sequence_num);
}
void Task::SetHeapHandle(HeapHandle heap_handle) {
if (!delayed_task_handle_delegate_)
return;
delayed_task_handle_delegate_->SetHeapHandle(heap_handle);
}
void Task::ClearHeapHandle() {
if (!delayed_task_handle_delegate_)
return;
delayed_task_handle_delegate_->ClearHeapHandle();
}
HeapHandle Task::GetHeapHandle() const {
if (!delayed_task_handle_delegate_)
return HeapHandle::Invalid();
return delayed_task_handle_delegate_->GetHeapHandle();
}
bool Task::IsCanceled() const {
CHECK(task);
if (task.IsCancelled()) {
return true;
}
return delayed_task_handle_delegate_.WasInvalidated();
}
bool Task::WillRunTask() {
if (delayed_task_handle_delegate_.WasInvalidated()) {
return false;
}
if (delayed_task_handle_delegate_) {
delayed_task_handle_delegate_->WillRunTask();
}
return true;
}
TimeTicks WakeUp::earliest_time() const {
if (delay_policy == subtle::DelayPolicy::kFlexiblePreferEarly)
return time - leeway;
return time;
}
TimeTicks WakeUp::latest_time() const {
if (delay_policy == subtle::DelayPolicy::kFlexibleNoSooner)
return time + leeway;
return time;
}
namespace internal {
PostedTask::PostedTask(
scoped_refptr<SequencedTaskRunner> task_runner,
OnceClosure callback,
Location location,
TimeDelta delay,
Nestable nestable,
TaskType task_type,
WeakPtr<DelayedTaskHandleDelegate> delayed_task_handle_delegate)
: callback(std::move(callback)),
location(location),
nestable(nestable),
task_type(task_type),
delay_or_delayed_run_time(delay),
task_runner(std::move(task_runner)),
delayed_task_handle_delegate(std::move(delayed_task_handle_delegate)) {}
PostedTask::PostedTask(
scoped_refptr<SequencedTaskRunner> task_runner,
OnceClosure callback,
Location location,
TimeTicks delayed_run_time,
subtle::DelayPolicy delay_policy,
Nestable nestable,
TaskType task_type,
WeakPtr<DelayedTaskHandleDelegate> delayed_task_handle_delegate)
: callback(std::move(callback)),
location(location),
nestable(nestable),
task_type(task_type),
delay_or_delayed_run_time(delayed_run_time),
delay_policy(delay_policy),
task_runner(std::move(task_runner)),
delayed_task_handle_delegate(std::move(delayed_task_handle_delegate)) {}
PostedTask::PostedTask(PostedTask&& move_from) noexcept = default;
PostedTask::~PostedTask() = default;
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,180 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TASKS_H_
#define BASE_TASK_SEQUENCE_MANAGER_TASKS_H_
#include <optional>
#include "base/base_export.h"
#include "base/check.h"
#include "base/containers/intrusive_heap.h"
#include "base/dcheck_is_on.h"
#include "base/pending_task.h"
#include "base/task/delay_policy.h"
#include "base/task/sequence_manager/delayed_task_handle_delegate.h"
#include "base/task/sequence_manager/enqueue_order.h"
#include "base/task/sequenced_task_runner.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
namespace base {
namespace sequence_manager {
using TaskType = uint8_t;
constexpr TaskType kTaskTypeNone = 0;
class TaskOrder;
namespace internal {
// Wrapper around PostTask method arguments and the assigned task type.
// Eventually it becomes a PendingTask once accepted by a TaskQueueImpl.
struct BASE_EXPORT PostedTask {
explicit PostedTask(scoped_refptr<SequencedTaskRunner> task_runner,
OnceClosure callback,
Location location,
TimeDelta delay = base::TimeDelta(),
Nestable nestable = Nestable::kNestable,
TaskType task_type = kTaskTypeNone,
WeakPtr<DelayedTaskHandleDelegate>
delayed_task_handle_delegate = nullptr);
explicit PostedTask(scoped_refptr<SequencedTaskRunner> task_runner,
OnceClosure callback,
Location location,
TimeTicks delayed_run_time,
subtle::DelayPolicy delay_policy,
Nestable nestable = Nestable::kNestable,
TaskType task_type = kTaskTypeNone,
WeakPtr<DelayedTaskHandleDelegate>
delayed_task_handle_delegate = nullptr);
PostedTask(PostedTask&& move_from) noexcept;
PostedTask(const PostedTask&) = delete;
PostedTask& operator=(const PostedTask&) = delete;
~PostedTask();
bool is_delayed() const {
return absl::holds_alternative<TimeTicks>(delay_or_delayed_run_time)
? !absl::get<TimeTicks>(delay_or_delayed_run_time).is_null()
: !absl::get<TimeDelta>(delay_or_delayed_run_time).is_zero();
}
OnceClosure callback;
Location location;
Nestable nestable = Nestable::kNestable;
TaskType task_type = kTaskTypeNone;
absl::variant<TimeDelta, TimeTicks> delay_or_delayed_run_time;
subtle::DelayPolicy delay_policy = subtle::DelayPolicy::kFlexibleNoSooner;
// The task runner this task is running on. Can be used by task runners that
// support posting back to the "current sequence".
scoped_refptr<SequencedTaskRunner> task_runner;
// The delegate for the DelayedTaskHandle, if this task was posted through
// PostCancelableDelayedTask(), nullptr otherwise.
WeakPtr<DelayedTaskHandleDelegate> delayed_task_handle_delegate;
};
} // namespace internal
enum class WakeUpResolution { kLow, kHigh };
// Represents a time at which a task wants to run.
struct WakeUp {
// is_null() for immediate wake up.
TimeTicks time;
// These are meaningless if is_immediate().
TimeDelta leeway;
WakeUpResolution resolution = WakeUpResolution::kLow;
subtle::DelayPolicy delay_policy = subtle::DelayPolicy::kFlexibleNoSooner;
bool operator!=(const WakeUp& other) const {
return time != other.time || leeway != other.leeway ||
resolution != other.resolution || delay_policy != other.delay_policy;
}
bool operator==(const WakeUp& other) const { return !(*this != other); }
bool is_immediate() const { return time.is_null(); }
TimeTicks earliest_time() const;
TimeTicks latest_time() const;
};
// PendingTask with extra metadata for SequenceManager.
struct BASE_EXPORT Task : public PendingTask {
Task(internal::PostedTask posted_task,
EnqueueOrder sequence_order,
EnqueueOrder enqueue_order = EnqueueOrder(),
TimeTicks queue_time = TimeTicks(),
WakeUpResolution wake_up_resolution = WakeUpResolution::kLow,
TimeDelta leeway = TimeDelta());
Task(Task&& move_from);
~Task();
Task& operator=(Task&& other);
// SequenceManager is particularly sensitive to enqueue order,
// so we have accessors for safety.
EnqueueOrder enqueue_order() const {
DCHECK(enqueue_order_);
return enqueue_order_;
}
void set_enqueue_order(EnqueueOrder enqueue_order) {
DCHECK(!enqueue_order_);
enqueue_order_ = enqueue_order;
}
bool enqueue_order_set() const { return enqueue_order_; }
TaskOrder task_order() const;
// OK to dispatch from a nested loop.
Nestable nestable = Nestable::kNonNestable;
// Needs high resolution timers.
bool is_high_res = false;
TaskType task_type;
// The task runner this task is running on. Can be used by task runners that
// support posting back to the "current sequence".
scoped_refptr<SequencedTaskRunner> task_runner;
#if DCHECK_IS_ON()
bool cross_thread_;
#endif
// Implement the intrusive heap contract.
void SetHeapHandle(HeapHandle heap_handle);
void ClearHeapHandle();
HeapHandle GetHeapHandle() const;
// Returns true if this task was canceled, either through weak pointer
// invalidation or through |delayed_task_handle_delegate_|.
bool IsCanceled() const;
// Must be invoked before running the task. Returns true if the task must run
// (any delayed task handle will have been invalidated by this method), false
// if it mustn't run (e.g. delayed task handle was invalidated prior to
// calling this method).
bool WillRunTask();
private:
// `enqueue_order_` is the primary component used to order tasks (see
// `TaskOrder`). For immediate tasks, `enqueue_order` is set when posted, but
// for delayed tasks it's not defined until they are enqueued. This is because
// otherwise delayed tasks could run before an immediate task posted after the
// delayed task.
EnqueueOrder enqueue_order_;
// The delegate for the DelayedTaskHandle, if this task was posted through
// `PostCancelableDelayedTask()`, not set otherwise. The task is canceled if
// `WeakPtr::WasInvalidated` is true. Note: if the task was not posted via
// `PostCancelableDelayedTask()`. the weak pointer won't be valid, but
// `WeakPtr::WasInvalidated` will be false.
WeakPtr<internal::DelayedTaskHandleDelegate> delayed_task_handle_delegate_;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TASKS_H_

View File

@@ -0,0 +1,47 @@
// Copyright 2018 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/test/fake_task.h"
namespace base {
namespace sequence_manager {
FakeTask::FakeTask() : FakeTask(0 /* task_type */) {}
FakeTask::FakeTask(TaskType task_type)
: Task(internal::PostedTask(nullptr,
OnceClosure(),
FROM_HERE,
base::TimeDelta(),
Nestable::kNestable,
task_type),
EnqueueOrder(),
EnqueueOrder(),
TimeTicks(),
WakeUpResolution::kLow) {}
FakeTaskTiming::FakeTaskTiming()
: TaskTiming(false /* has_wall_time */, false /* has_thread_time */) {}
FakeTaskTiming::FakeTaskTiming(TimeTicks start, TimeTicks end)
: FakeTaskTiming() {
has_wall_time_ = true;
start_time_ = start;
end_time_ = end;
state_ = State::Finished;
}
FakeTaskTiming::FakeTaskTiming(TimeTicks start,
TimeTicks end,
ThreadTicks thread_start,
ThreadTicks thread_end)
: FakeTaskTiming(start, end) {
has_thread_time_ = true;
start_thread_time_ = thread_start;
end_thread_time_ = thread_end;
state_ = State::Finished;
}
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,33 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_FAKE_TASK_H_
#define BASE_TASK_SEQUENCE_MANAGER_TEST_FAKE_TASK_H_
#include "base/task/sequence_manager/task_queue.h"
#include "base/task/sequence_manager/tasks.h"
namespace base {
namespace sequence_manager {
class FakeTask : public Task {
public:
FakeTask();
explicit FakeTask(TaskType task_type);
};
class FakeTaskTiming : public TaskQueue::TaskTiming {
public:
FakeTaskTiming();
FakeTaskTiming(TimeTicks start, TimeTicks end);
FakeTaskTiming(TimeTicks start,
TimeTicks end,
ThreadTicks thread_start,
ThreadTicks thread_end);
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_FAKE_TASK_H_

View File

@@ -0,0 +1,36 @@
// Copyright 2018 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/test/mock_time_domain.h"
#include <optional>
namespace base {
namespace sequence_manager {
MockTimeDomain::MockTimeDomain(TimeTicks initial_now_ticks)
: now_ticks_(initial_now_ticks) {}
MockTimeDomain::~MockTimeDomain() = default;
TimeTicks MockTimeDomain::NowTicks() const {
return now_ticks_;
}
void MockTimeDomain::SetNowTicks(TimeTicks now_ticks) {
now_ticks_ = now_ticks;
}
bool MockTimeDomain::MaybeFastForwardToWakeUp(
std::optional<WakeUp> next_wake_up,
bool quit_when_idle_requested) {
return false;
}
const char* MockTimeDomain::GetName() const {
return "MockTimeDomain";
}
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,42 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_DOMAIN_H_
#define BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_DOMAIN_H_
#include <optional>
#include "base/task/sequence_manager/time_domain.h"
#include "base/time/tick_clock.h"
namespace base {
namespace sequence_manager {
// TimeDomain with a mock clock and not invoking SequenceManager.
// NOTE: All methods are main thread only.
class MockTimeDomain : public TimeDomain {
public:
explicit MockTimeDomain(TimeTicks initial_now_ticks);
MockTimeDomain(const MockTimeDomain&) = delete;
MockTimeDomain& operator=(const MockTimeDomain&) = delete;
~MockTimeDomain() override;
void SetNowTicks(TimeTicks now_ticks);
// TickClock implementation:
TimeTicks NowTicks() const override;
// TimeDomain implementation:
bool MaybeFastForwardToWakeUp(std::optional<WakeUp> next_wake_up,
bool quit_when_idle_requested) override;
const char* GetName() const override;
private:
TimeTicks now_ticks_;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_DOMAIN_H_

View File

@@ -0,0 +1,86 @@
// Copyright 2019 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/test/mock_time_message_pump.h"
#include <algorithm>
#include <ostream>
#include "base/auto_reset.h"
#include "base/notreached.h"
#include "base/test/simple_test_tick_clock.h"
namespace base {
namespace sequence_manager {
MockTimeMessagePump::MockTimeMessagePump(SimpleTestTickClock* clock)
: clock_(clock) {}
MockTimeMessagePump::~MockTimeMessagePump() = default;
bool MockTimeMessagePump::MaybeAdvanceTime(TimeTicks target_time) {
auto now = clock_->NowTicks();
if (target_time <= now)
return true;
TimeTicks next_now;
if (!target_time.is_max()) {
next_now = std::min(allow_advance_until_, target_time);
} else if (allow_advance_until_ == TimeTicks::Max()) {
next_now = now;
} else {
next_now = allow_advance_until_;
}
if (now < next_now) {
clock_->SetNowTicks(next_now);
return true;
}
return false;
}
void MockTimeMessagePump::Run(Delegate* delegate) {
AutoReset<bool> auto_reset_keep_running(&keep_running_, true);
for (;;) {
Delegate::NextWorkInfo info = delegate->DoWork();
if (!keep_running_ || quit_after_do_some_work_)
break;
if (info.is_immediate())
continue;
delegate->DoIdleWork();
if (!keep_running_)
break;
if (MaybeAdvanceTime(info.delayed_run_time))
continue;
next_wake_up_time_ = info.delayed_run_time;
if (stop_when_message_pump_is_idle_)
return;
NOTREACHED() << "Pump would go to sleep. Probably not what you wanted, "
"consider rewriting your test.";
}
}
void MockTimeMessagePump::Quit() {
keep_running_ = false;
}
void MockTimeMessagePump::ScheduleWork() {}
void MockTimeMessagePump::ScheduleDelayedWork(
const Delegate::NextWorkInfo& next_work_info) {
next_wake_up_time_ = next_work_info.delayed_run_time;
}
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,87 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_MESSAGE_PUMP_H_
#define BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_MESSAGE_PUMP_H_
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/message_loop/message_pump.h"
#include "base/synchronization/waitable_event.h"
#include "base/time/time.h"
namespace base {
class SimpleTestTickClock;
namespace sequence_manager {
// MessagePump implementation that uses a SimpleTestTickClock to keep track of
// time and will advance it as needed to keep running tasks.
//
// This pump will actually check fail if it ever has to go to sleep as this
// would indicate that the unit test might block indefinitely.
// TODO(carlscab): In the future we could consider sleeping if there is no
// outstanding |delayed_work_time_|, because we could be woken up by concurrent
// ScheduleWork() calls.
class MockTimeMessagePump : public MessagePump {
public:
explicit MockTimeMessagePump(SimpleTestTickClock* clock);
~MockTimeMessagePump() override;
// MessagePump implementation
void Run(Delegate* delegate) override;
void Quit() override;
void ScheduleWork() override;
void ScheduleDelayedWork(
const Delegate::NextWorkInfo& next_work_info) override;
// Returns the time at which the pump would have to wake up to be perform
// work.
TimeTicks next_wake_up_time() const { return next_wake_up_time_; }
// Quits after the first call to Delegate::DoWork(). Useful
// for tests that want to make sure certain things happen during a DoWork
// call.
void SetQuitAfterDoWork(bool quit_after_do_some_work) {
quit_after_do_some_work_ = quit_after_do_some_work;
}
// Allows this instance to advance the SimpleTestTickClock up to but not over
// |advance_until| when idle (i.e. when a regular pump would go to sleep).
// The clock will allways be advanced to |advance_until|, even if there are no
// tasks requiring it (i.e. delayed tasks to be run after
// |advance_until|) except for a value of TimeTicks::Max() which will advance
// the clock as long as there is pending delayed work.
void SetAllowTimeToAutoAdvanceUntil(TimeTicks advance_until) {
allow_advance_until_ = advance_until;
}
// Quit when this pump's Delegate is out of work (i.e. when a regular pump
// would go to sleep) and we are not allowed to advance the clock anymore.
void SetStopWhenMessagePumpIsIdle(bool stop_when_message_pump_is_idle) {
stop_when_message_pump_is_idle_ = stop_when_message_pump_is_idle;
}
private:
// Returns true if the clock was indeed advanced and thus we should attempt
// another iteration of the DoWork-DoIdleWork-loop.
bool MaybeAdvanceTime(TimeTicks target_time);
const raw_ptr<SimpleTestTickClock> clock_;
// This flag is set to false when Run should return.
bool keep_running_ = true;
bool stop_when_message_pump_is_idle_ = false;
bool quit_after_do_some_work_ = false;
TimeTicks next_wake_up_time_{TimeTicks::Max()};
TimeTicks allow_advance_until_ = TimeTicks::Min();
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_MESSAGE_PUMP_H_

View File

@@ -0,0 +1,69 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_SEQUENCE_MANAGER_FOR_TEST_H_
#define BASE_TASK_SEQUENCE_MANAGER_TEST_SEQUENCE_MANAGER_FOR_TEST_H_
#include <memory>
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/tick_clock.h"
namespace base {
namespace sequence_manager {
class SequenceManagerForTest : public internal::SequenceManagerImpl {
public:
~SequenceManagerForTest() override = default;
// Creates SequenceManagerForTest using ThreadControllerImpl constructed with
// the given arguments. ThreadControllerImpl is slightly overridden to skip
// nesting observers registration if message loop is absent.
static std::unique_ptr<SequenceManagerForTest> Create(
SequenceManagerImpl* funneled_sequence_manager,
scoped_refptr<SingleThreadTaskRunner> task_runner,
const TickClock* clock,
// Since most test calls are in Blink, randomised sampling is enabled
// by default in the test SequenceManager, as opposed to production code.
SequenceManager::Settings settings =
SequenceManager::Settings::Builder()
.SetRandomisedSamplingEnabled(true)
.Build());
// Creates SequenceManagerForTest using the provided ThreadController.
static std::unique_ptr<SequenceManagerForTest> Create(
std::unique_ptr<internal::ThreadController> thread_controller,
SequenceManager::Settings settings =
SequenceManager::Settings::Builder()
.SetRandomisedSamplingEnabled(true)
.Build());
static std::unique_ptr<SequenceManagerForTest> CreateOnCurrentThread(
SequenceManager::Settings);
size_t ActiveQueuesCount() const;
bool HasImmediateWork() const;
size_t PendingTasksCount() const;
size_t QueuesToDeleteCount() const;
size_t QueuesToShutdownCount();
using internal::SequenceManagerImpl::
CreateThreadControllerImplForCurrentThread;
using internal::SequenceManagerImpl::GetNextSequenceNumber;
using internal::SequenceManagerImpl::MoveReadyDelayedTasksToWorkQueues;
using internal::SequenceManagerImpl::ReloadEmptyWorkQueues;
private:
explicit SequenceManagerForTest(
std::unique_ptr<internal::ThreadController> thread_controller,
SequenceManager::Settings settings);
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_SEQUENCE_MANAGER_FOR_TEST_H_

View File

@@ -0,0 +1,23 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_TIME_OBSERVER_H_
#define BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_TIME_OBSERVER_H_
#include "base/task/sequence_manager/task_time_observer.h"
#include "base/time/time.h"
namespace base {
namespace sequence_manager {
class TestTaskTimeObserver : public TaskTimeObserver {
public:
void WillProcessTask(TimeTicks start_time) override {}
void DidProcessTask(TimeTicks start_time, TimeTicks end_time) override {}
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_TIME_OBSERVER_H_

View File

@@ -0,0 +1,728 @@
// Copyright 2020 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/thread_controller.h"
#include <atomic>
#include <string_view>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
namespace base {
namespace sequence_manager {
namespace internal {
namespace {
// Enable sample metadata recording in this class, if it's currently disabled.
// Note that even if `kThreadControllerSetsProfilerMetadata` is disabled, sample
// metadata may still be recorded.
BASE_FEATURE(kThreadControllerSetsProfilerMetadata,
"ThreadControllerSetsProfilerMetadata",
base::FEATURE_DISABLED_BY_DEFAULT);
// Thread safe copy to be updated once feature list is available. This
// defaults to true to make sure that no metadata is lost on clients that
// need to record. This leads to some overeporting before feature list
// initialization on other clients but that's still way better than the current
// situation which is reporting all the time.
std::atomic<bool> g_thread_controller_sets_profiler_metadata{true};
// ThreadController interval metrics are mostly of interest for intervals that
// are not trivially short. Under a certain threshold it's unlikely that
// intervention from developers would move metrics. Log with suffix for
// intervals under a threshold chosen via tracing data. To validate the
// threshold makes sense and does not filter out too many samples
// ThreadController.ActiveIntervalDuration can be used.
constexpr TimeDelta kNonTrivialActiveIntervalLength = Milliseconds(1);
constexpr TimeDelta kMediumActiveIntervalLength = Milliseconds(100);
std::string MakeSuffix(std::string_view time_suffix,
std::string_view thread_name) {
return base::StrCat({".", time_suffix, ".", thread_name});
}
} // namespace
ThreadController::ThreadController(const TickClock* time_source)
: associated_thread_(AssociatedThreadId::CreateUnbound()),
time_source_(time_source) {}
ThreadController::~ThreadController() = default;
void ThreadController::SetTickClock(const TickClock* clock) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
time_source_ = clock;
}
ThreadController::RunLevelTracker::RunLevelTracker(
const ThreadController& outer)
: outer_(outer) {}
ThreadController::RunLevelTracker::~RunLevelTracker() {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
// There shouldn't be any remaining |run_levels_| by the time this unwinds.
DCHECK_EQ(run_levels_.size(), 0u);
}
// static
void ThreadController::InitializeFeatures(
features::EmitThreadControllerProfilerMetadata emit_profiler_metadata) {
g_thread_controller_sets_profiler_metadata.store(
emit_profiler_metadata ==
features::EmitThreadControllerProfilerMetadata::kForce ||
base::FeatureList::IsEnabled(kThreadControllerSetsProfilerMetadata),
std::memory_order_relaxed);
}
bool ThreadController::RunLevelTracker::RunLevel::ShouldRecordSampleMetadata() {
return g_thread_controller_sets_profiler_metadata.load(
std::memory_order_relaxed);
}
std::string_view ThreadController::RunLevelTracker::RunLevel::GetThreadName() {
std::string_view thread_name = "Other";
if (!time_keeper_->thread_name().empty()) {
thread_name = time_keeper_->thread_name();
}
return thread_name;
}
std::string
ThreadController::RunLevelTracker::RunLevel::GetSuffixForCatchAllHistogram() {
return MakeSuffix("Any", GetThreadName());
}
std::string ThreadController::RunLevelTracker::RunLevel::GetSuffixForHistogram(
TimeDelta duration) {
std::string_view time_suffix;
if (duration < kNonTrivialActiveIntervalLength) {
time_suffix = "Short";
} else if (duration < kMediumActiveIntervalLength) {
time_suffix = "Medium";
}
return MakeSuffix(time_suffix, GetThreadName());
}
void ThreadController::EnableMessagePumpTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing) {
// MessagePump runs too fast, a low-res clock would result in noisy metrics.
if (!base::TimeTicks::IsHighResolution())
return;
run_level_tracker_.EnableTimeKeeperMetrics(
thread_name, wall_time_based_metrics_enabled_for_testing);
}
void ThreadController::RunLevelTracker::EnableTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing) {
time_keeper_.EnableRecording(thread_name,
wall_time_based_metrics_enabled_for_testing);
}
void ThreadController::RunLevelTracker::TimeKeeper::EnableRecording(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing) {
DCHECK(!histogram_);
thread_name_ = thread_name;
wall_time_based_metrics_enabled_for_testing_ =
wall_time_based_metrics_enabled_for_testing;
histogram_ = LinearHistogram::FactoryGet(
JoinString({"Scheduling.MessagePumpTimeKeeper", thread_name}, "."), 1,
Phase::kLastPhase, Phase::kLastPhase + 1,
base::HistogramBase::kUmaTargetedHistogramFlag);
#if BUILDFLAG(ENABLE_BASE_TRACING)
perfetto_track_.emplace(
reinterpret_cast<uint64_t>(this),
// TODO(crbug.com/42050015): Replace with ThreadTrack::Current() after SDK
// migration.
// In the non-SDK version, ThreadTrack::Current() returns a different
// track id on some platforms (for example Mac OS), which results in
// async tracks not being associated with their thread.
perfetto::ThreadTrack::ForThread(base::PlatformThread::CurrentId()));
// TODO(crbug.com/42050015): Use Perfetto library to name this Track.
// auto desc = perfetto_track_->Serialize();
// desc.set_name(JoinString({"MessagePumpPhases", thread_name}, " "));
// perfetto::internal::TrackEventDataSource::SetTrackDescriptor(
// *perfetto_track_, desc);
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
}
void ThreadController::RunLevelTracker::OnRunLoopStarted(State initial_state,
LazyNow& lazy_now) {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
const bool is_nested = !run_levels_.empty();
run_levels_.emplace(initial_state, is_nested, time_keeper_, lazy_now);
// In unit tests, RunLoop::Run() acts as the initial wake-up.
if (!is_nested && initial_state != kIdle)
time_keeper_.RecordWakeUp(lazy_now);
}
void ThreadController::RunLevelTracker::OnRunLoopEnded() {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
// Normally this will occur while kIdle or kInBetweenWorkItems but it can also
// occur while kRunningWorkItem in rare situations where the owning
// ThreadController is deleted from within a task. Ref.
// SequenceManagerWithTaskRunnerTest.DeleteSequenceManagerInsideATask. Thus we
// can't assert anything about the current state other than that it must be
// exiting an existing RunLevel.
DCHECK(!run_levels_.empty());
LazyNow exit_lazy_now(outer_->time_source_);
run_levels_.top().set_exit_lazy_now(&exit_lazy_now);
run_levels_.pop();
}
void ThreadController::RunLevelTracker::OnWorkStarted(LazyNow& lazy_now) {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
// Ignore work outside the main run loop.
// The only practical case where this would happen is if a native loop is spun
// outside the main runloop (e.g. system dialog during startup). We cannot
// support this because we are not guaranteed to be able to observe its exit
// (like we would inside an application task which is at least guaranteed to
// itself notify us when it ends). Some ThreadControllerWithMessagePumpTest
// also drive ThreadController outside a RunLoop and hit this.
if (run_levels_.empty())
return;
// Already running a work item? => #work-in-work-implies-nested
if (run_levels_.top().state() == kRunningWorkItem) {
run_levels_.emplace(kRunningWorkItem, /*nested=*/true, time_keeper_,
lazy_now);
} else {
if (run_levels_.top().state() == kIdle) {
time_keeper_.RecordWakeUp(lazy_now);
} else {
time_keeper_.RecordEndOfPhase(kPumpOverhead, lazy_now);
}
// Going from kIdle or kInBetweenWorkItems to kRunningWorkItem.
run_levels_.top().UpdateState(kRunningWorkItem, lazy_now);
}
}
void ThreadController::RunLevelTracker::OnApplicationTaskSelected(
TimeTicks queue_time,
LazyNow& lazy_now) {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
// As-in OnWorkStarted. Early native loops can result in
// ThreadController::DoWork because the lack of a top-level RunLoop means
// `task_execution_allowed` wasn't consumed.
if (run_levels_.empty())
return;
// OnWorkStarted() is expected to precede OnApplicationTaskSelected().
DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
time_keeper_.OnApplicationTaskSelected(queue_time, lazy_now);
}
void ThreadController::RunLevelTracker::OnWorkEnded(LazyNow& lazy_now,
int run_level_depth) {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
if (run_levels_.empty())
return;
// #done-work-at-lower-runlevel-implies-done-nested
if (run_level_depth != static_cast<int>(num_run_levels())) {
DCHECK_EQ(run_level_depth + 1, static_cast<int>(num_run_levels()));
run_levels_.top().set_exit_lazy_now(&lazy_now);
run_levels_.pop();
} else {
time_keeper_.RecordEndOfPhase(kWorkItem, lazy_now);
}
// Whether we exited a nested run-level or not: the current run-level is now
// transitioning from kRunningWorkItem to kInBetweenWorkItems.
DCHECK_EQ(run_levels_.top().state(), kRunningWorkItem);
run_levels_.top().UpdateState(kInBetweenWorkItems, lazy_now);
}
void ThreadController::RunLevelTracker::OnIdle(LazyNow& lazy_now) {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
if (run_levels_.empty())
return;
DCHECK_NE(run_levels_.top().state(), kRunningWorkItem);
time_keeper_.RecordEndOfPhase(kIdleWork, lazy_now);
run_levels_.top().UpdateState(kIdle, lazy_now);
}
void ThreadController::RunLevelTracker::RecordScheduleWork() {
// Matching TerminatingFlow is found at
// ThreadController::RunLevelTracker::RunLevel::UpdateState
if (outer_->associated_thread_->IsBoundToCurrentThread()) {
TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWorkToSelf");
} else {
TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWork",
perfetto::Flow::FromPointer(this));
}
}
// static
void ThreadController::RunLevelTracker::SetTraceObserverForTesting(
TraceObserverForTesting* trace_observer_for_testing) {
DCHECK_NE(!!trace_observer_for_testing_, !!trace_observer_for_testing);
trace_observer_for_testing_ = trace_observer_for_testing;
}
// static
ThreadController::RunLevelTracker::TraceObserverForTesting*
ThreadController::RunLevelTracker::trace_observer_for_testing_ = nullptr;
ThreadController::RunLevelTracker::RunLevel::RunLevel(State initial_state,
bool is_nested,
TimeKeeper& time_keeper,
LazyNow& lazy_now)
: is_nested_(is_nested),
time_keeper_(time_keeper),
thread_controller_sample_metadata_("ThreadController active",
base::SampleMetadataScope::kThread) {
if (is_nested_) {
// Stop the current kWorkItem phase now, it will resume after the kNested
// phase ends.
time_keeper_->RecordEndOfPhase(kWorkItemSuspendedOnNested, lazy_now);
}
UpdateState(initial_state, lazy_now);
}
ThreadController::RunLevelTracker::RunLevel::~RunLevel() {
if (!was_moved_) {
DCHECK(exit_lazy_now_);
UpdateState(kIdle, *exit_lazy_now_);
if (is_nested_) {
// Attribute the entire time in this nested RunLevel to kNested phase. If
// this wasn't the last nested RunLevel, this is ignored and will be
// applied on the final pop().
time_keeper_->RecordEndOfPhase(kNested, *exit_lazy_now_);
if (ShouldRecordSampleMetadata()) {
// Intentionally ordered after UpdateState(kIdle), reinstantiates
// thread_controller_sample_metadata_ when yielding back to a parent
// RunLevel (which is active by definition as it is currently running
// this one).
thread_controller_sample_metadata_.Set(
static_cast<int64_t>(++thread_controller_active_id_));
}
}
}
}
ThreadController::RunLevelTracker::RunLevel::RunLevel(RunLevel&& other) =
default;
void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
const char* name,
int percentage) {
UmaHistogramPercentage(base::StrCat({name, ".", GetThreadName()}),
percentage);
}
void ThreadController::RunLevelTracker::RunLevel::LogPercentageMetric(
const char* name,
int percentage,
base::TimeDelta interval_duration) {
UmaHistogramPercentage(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
percentage);
UmaHistogramPercentage(
base::StrCat({name, GetSuffixForHistogram(interval_duration)}),
percentage);
}
void ThreadController::RunLevelTracker::RunLevel::LogIntervalMetric(
const char* name,
base::TimeDelta value,
base::TimeDelta interval_duration) {
// Log towards "Any" time suffix first.
UmaHistogramTimes(base::StrCat({name, GetSuffixForCatchAllHistogram()}),
value);
if (interval_duration < kNonTrivialActiveIntervalLength) {
UmaHistogramCustomMicrosecondsTimes(
base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
base::Microseconds(1), kNonTrivialActiveIntervalLength, 100);
} else if (interval_duration < kMediumActiveIntervalLength) {
UmaHistogramCustomTimes(
base::StrCat({name, GetSuffixForHistogram(interval_duration)}), value,
kNonTrivialActiveIntervalLength, kMediumActiveIntervalLength, 100);
}
}
void ThreadController::RunLevelTracker::RunLevel::LogOnActiveMetrics(
LazyNow& lazy_now) {
CHECK(last_active_start_.is_null());
CHECK(last_active_threadtick_start_.is_null());
if (!last_active_end_.is_null()) {
const base::TimeDelta idle_time = lazy_now.Now() - last_active_end_;
LogIntervalMetric("Scheduling.ThreadController.IdleDuration", idle_time,
idle_time);
last_active_end_ = base::TimeTicks();
accumulated_idle_time_ += idle_time;
}
// Taking thread ticks can be expensive. Make sure to do it rarely enough to
// not have a discernible impact on performance.
static const bool thread_ticks_supported = ThreadTicks::IsSupported();
// Disable subsampling to support wall-time based metrics. Only supported for
// testing purposes. By default, the subsampling probability is 0.1%.
const double probability =
time_keeper_->wall_time_based_metrics_enabled_for_testing() ? 1.0 : 0.001;
if (thread_ticks_supported &&
metrics_sub_sampler_.ShouldSample(probability)) {
last_active_start_ = lazy_now.Now();
last_active_threadtick_start_ = ThreadTicks::Now();
}
}
void ThreadController::RunLevelTracker::RunLevel::LogOnIdleMetrics(
LazyNow& lazy_now) {
if (!last_active_start_.is_null()) {
const base::TimeDelta elapsed_ticks = lazy_now.Now() - last_active_start_;
base::TimeDelta elapsed_thread_ticks =
ThreadTicks::Now() - last_active_threadtick_start_;
// Round to 100% in case of clock imprecisions making it look like
// there's impossibly more ThreadTicks than TimeTicks elapsed.
elapsed_thread_ticks = std::min(elapsed_thread_ticks, elapsed_ticks);
LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalDuration",
elapsed_ticks, elapsed_ticks);
LogIntervalMetric(
"Scheduling.ThreadController.ActiveIntervalOffCpuDuration",
elapsed_ticks - elapsed_thread_ticks, elapsed_ticks);
LogIntervalMetric("Scheduling.ThreadController.ActiveIntervalOnCpuDuration",
elapsed_thread_ticks, elapsed_ticks);
// If the interval was shorter than a tick, 100% on-cpu time is assumed.
int active_interval_cpu_percentage =
elapsed_ticks.is_zero()
? 100
: static_cast<int>(
(elapsed_thread_ticks * 100).IntDiv(elapsed_ticks));
LogPercentageMetric(
"Scheduling.ThreadController.ActiveIntervalOnCpuPercentage",
active_interval_cpu_percentage, elapsed_ticks);
if (time_keeper_->wall_time_based_metrics_enabled_for_testing()) {
accumulated_active_time_ += elapsed_ticks;
accumulated_active_on_cpu_time_ += elapsed_thread_ticks;
accumulated_active_off_cpu_time_ +=
(elapsed_ticks - elapsed_thread_ticks);
// Accumulated wall-time since last wall-time based metric was stored.
const base::TimeDelta accumulated_wall_time =
accumulated_active_time_ + accumulated_idle_time_;
// Add wall-time based ratio metrics (in percent) when the total sum of
// active and idle times is larger than one second.
if (accumulated_wall_time > Seconds(1)) {
const int active_vs_wall_time_percentage = checked_cast<int>(
(accumulated_active_time_ * 100).IntDiv(accumulated_wall_time));
LogPercentageMetric(
"Scheduling.ThreadController.ActiveVsWallTimePercentage",
active_vs_wall_time_percentage);
const int active_on_cpu_vs_wall_time_percentage =
checked_cast<int>((accumulated_active_on_cpu_time_ * 100)
.IntDiv(accumulated_wall_time));
LogPercentageMetric(
"Scheduling.ThreadController.ActiveOnCpuVsWallTimePercentage",
active_on_cpu_vs_wall_time_percentage);
const int active_off_cpu_vs_wall_time_percentage =
checked_cast<int>((accumulated_active_off_cpu_time_ * 100)
.IntDiv(accumulated_wall_time));
LogPercentageMetric(
"Scheduling.ThreadController.ActiveOffCpuVsWallTimePercentage",
active_off_cpu_vs_wall_time_percentage);
accumulated_idle_time_ = base::TimeDelta();
accumulated_active_time_ = base::TimeDelta();
accumulated_active_on_cpu_time_ = base::TimeDelta();
accumulated_active_off_cpu_time_ = base::TimeDelta();
}
}
// Reset timings.
last_active_start_ = base::TimeTicks();
last_active_threadtick_start_ = base::ThreadTicks();
last_active_end_ = lazy_now.Now();
}
}
void ThreadController::RunLevelTracker::RunLevel::UpdateState(
State new_state,
LazyNow& lazy_now) {
// The only state that can be redeclared is idle, anything else should be a
// transition.
DCHECK(state_ != new_state || new_state == kIdle)
<< state_ << "," << new_state;
const bool was_active = state_ != kIdle;
const bool is_active = new_state != kIdle;
state_ = new_state;
if (was_active == is_active)
return;
// Change of state.
if (is_active) {
LogOnActiveMetrics(lazy_now);
// Flow emission is found at
// ThreadController::RunLevelTracker::RecordScheduleWork.
TRACE_EVENT_BEGIN("base", "ThreadController active", lazy_now.Now(),
[&](perfetto::EventContext& ctx) {
time_keeper_->MaybeEmitIncomingWakeupFlow(ctx);
});
if (ShouldRecordSampleMetadata()) {
// Overriding the annotation from the previous RunLevel is intentional.
// Only the top RunLevel is ever updated, which holds the relevant state.
thread_controller_sample_metadata_.Set(
static_cast<int64_t>(++thread_controller_active_id_));
}
} else {
if (ShouldRecordSampleMetadata()) {
thread_controller_sample_metadata_.Remove();
}
LogOnIdleMetrics(lazy_now);
TRACE_EVENT_END("base", lazy_now.Now());
}
if (trace_observer_for_testing_) {
if (is_active)
trace_observer_for_testing_->OnThreadControllerActiveBegin();
else
trace_observer_for_testing_->OnThreadControllerActiveEnd();
}
}
ThreadController::RunLevelTracker::TimeKeeper::TimeKeeper(
const RunLevelTracker& outer)
: outer_(outer) {}
void ThreadController::RunLevelTracker::TimeKeeper::RecordWakeUp(
LazyNow& lazy_now) {
if (!ShouldRecordNow(ShouldRecordReqs::kOnWakeUp))
return;
// Phase::kScheduled will be accounted against `last_wakeup_` in
// OnTaskSelected, if there's an application task in this work cycle.
last_wakeup_ = lazy_now.Now();
// Account the next phase starting from now.
last_phase_end_ = last_wakeup_;
#if BUILDFLAG(ENABLE_BASE_TRACING)
// Emit the END of the kScheduled phase right away, this avoids incorrect
// ordering when kScheduled is later emitted and its END matches the BEGIN of
// an already emitted phase (tracing's sort is stable and would keep the late
// END for kScheduled after the earlier BEGIN of the next phase):
// crbug.com/1333460. As we just woke up, there are no events active at this
// point (we don't record MessagePumpPhases while nested). In the absence of
// a kScheduled phase, this unmatched END will be ignored.
TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
last_wakeup_);
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
}
void ThreadController::RunLevelTracker::TimeKeeper::OnApplicationTaskSelected(
TimeTicks queue_time,
LazyNow& lazy_now) {
if (!ShouldRecordNow())
return;
if (!last_wakeup_.is_null()) {
// `queue_time` can be null on threads that did not
// `SetAddQueueTimeToTasks(true)`. `queue_time` can also be ahead of
// `last_wakeup` in racy cases where the first chrome task is enqueued
// while the pump was already awake (e.g. for native work). Consider the
// kScheduled phase inexistent in that case.
if (!queue_time.is_null() && queue_time < last_wakeup_) {
if (!last_sleep_.is_null() && queue_time < last_sleep_) {
// Avoid overlapping kScheduled and kIdleWork phases when work is
// scheduled while going to sleep.
queue_time = last_sleep_;
}
RecordTimeInPhase(kScheduled, queue_time, last_wakeup_);
#if BUILDFLAG(ENABLE_BASE_TRACING)
// Match the END event which was already emitted by RecordWakeUp().
TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
perfetto::StaticString(PhaseToEventName(kScheduled)),
*perfetto_track_, queue_time);
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
}
last_wakeup_ = TimeTicks();
}
RecordEndOfPhase(kSelectingApplicationTask, lazy_now);
current_work_item_is_native_ = false;
}
void ThreadController::RunLevelTracker::TimeKeeper::RecordEndOfPhase(
Phase phase,
LazyNow& lazy_now) {
if (!ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
: ShouldRecordReqs::kRegular)) {
return;
}
if (phase == kWorkItem && !current_work_item_is_native_) {
phase = kApplicationTask;
// Back to assuming future work is native until OnApplicationTaskSelected()
// is invoked.
current_work_item_is_native_ = true;
} else if (phase == kWorkItemSuspendedOnNested) {
// kWorkItemSuspendedOnNested temporarily marks the end of time allocated to
// the current work item. It is reported as a separate phase to skip the
// above `current_work_item_is_native_ = true` which assumes the work item
// is truly complete.
phase = current_work_item_is_native_ ? kNativeWork : kApplicationTask;
}
const TimeTicks phase_end = lazy_now.Now();
RecordTimeInPhase(phase, last_phase_end_, phase_end);
#if BUILDFLAG(ENABLE_BASE_TRACING)
// Ugly hack to name our `perfetto_track_`.
bool is_tracing_enabled = false;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("base"),
&is_tracing_enabled);
if (is_tracing_enabled) {
if (!was_tracing_enabled_) {
// The first event name on the track hackily names the track...
// TODO(crbug.com/42050015): Use the Perfetto library to properly name
// this Track in EnableRecording above.
TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("base"),
"MessagePumpPhases", *perfetto_track_,
last_phase_end_ - Seconds(1));
}
const char* event_name = PhaseToEventName(phase);
TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("base"),
perfetto::StaticString(event_name), *perfetto_track_,
last_phase_end_);
TRACE_EVENT_END(TRACE_DISABLED_BY_DEFAULT("base"), *perfetto_track_,
phase_end);
}
was_tracing_enabled_ = is_tracing_enabled;
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
last_phase_end_ = phase_end;
}
void ThreadController::RunLevelTracker::TimeKeeper::MaybeEmitIncomingWakeupFlow(
perfetto::EventContext& ctx) {
#if BUILDFLAG(ENABLE_BASE_TRACING)
static const uint8_t* flow_enabled =
TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("wakeup.flow");
if (!*flow_enabled) {
return;
}
perfetto::TerminatingFlow::ProcessScoped(
reinterpret_cast<uint64_t>(&(outer_.get())))(ctx);
#endif
}
bool ThreadController::RunLevelTracker::TimeKeeper::ShouldRecordNow(
ShouldRecordReqs reqs) {
DCHECK_CALLED_ON_VALID_THREAD(
outer_->outer_->associated_thread_->thread_checker);
// Recording is technically enabled once `histogram_` is set, however
// `last_phase_end_` will be null until the next RecordWakeUp in the work
// cycle in which `histogram_` is enabled. Only start recording from there.
// Ignore any nested phases. `reqs` may indicate exceptions to this.
//
// TODO(crbug.com/40226913): In a follow-up, we could probably always be
// tracking the phases of the pump and merely ignore the reporting if
// `histogram_` isn't set.
switch (reqs) {
case ShouldRecordReqs::kRegular:
return histogram_ && !last_phase_end_.is_null() &&
outer_->run_levels_.size() == 1;
case ShouldRecordReqs::kOnWakeUp:
return histogram_ && outer_->run_levels_.size() == 1;
case ShouldRecordReqs::kOnEndNested:
return histogram_ && !last_phase_end_.is_null() &&
outer_->run_levels_.size() <= 2;
}
}
void ThreadController::RunLevelTracker::TimeKeeper::RecordTimeInPhase(
Phase phase,
TimeTicks phase_begin,
TimeTicks phase_end) {
DCHECK(ShouldRecordNow(phase == kNested ? ShouldRecordReqs::kOnEndNested
: ShouldRecordReqs::kRegular));
// Report a phase only when at least 100ms has been attributed to it.
static constexpr auto kReportInterval = Milliseconds(100);
// Above 30s in a single phase, assume suspend-resume and ignore the report.
static constexpr auto kSkippedDelta = Seconds(30);
const auto delta = phase_end - phase_begin;
DCHECK(!delta.is_negative()) << delta;
if (delta >= kSkippedDelta)
return;
deltas_[phase] += delta;
if (deltas_[phase] >= kReportInterval) {
const int count = deltas_[phase] / Milliseconds(1);
histogram_->AddCount(phase, count);
deltas_[phase] -= Milliseconds(count);
}
if (phase == kIdleWork)
last_sleep_ = phase_end;
if (outer_->trace_observer_for_testing_)
outer_->trace_observer_for_testing_->OnPhaseRecorded(phase);
}
// static
const char* ThreadController::RunLevelTracker::TimeKeeper::PhaseToEventName(
Phase phase) {
switch (phase) {
case kScheduled:
return "Scheduled";
case kPumpOverhead:
return "PumpOverhead";
case kNativeWork:
return "NativeTask";
case kSelectingApplicationTask:
return "SelectingApplicationTask";
case kApplicationTask:
return "ApplicationTask";
case kIdleWork:
return "IdleWork";
case kNested:
return "Nested";
case kWorkItemSuspendedOnNested:
// kWorkItemSuspendedOnNested should be transformed into kNativeWork or
// kApplicationTask before this point.
NOTREACHED();
}
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,488 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
#include <optional>
#include <stack>
#include <string>
#include <string_view>
#include <vector>
#include "base/base_export.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/features.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/message_pump.h"
#include "base/profiler/sample_metadata.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/associated_thread_id.h"
#include "base/task/sequence_manager/tasks.h"
#include "base/task/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
#include "base/tracing_buildflags.h"
#include "build/build_config.h"
namespace base {
class HistogramBase;
class MessageLoopBase;
class TickClock;
struct PendingTask;
namespace sequence_manager {
namespace internal {
class SequencedTaskSource;
// Implementation of this interface is used by SequenceManager to schedule
// actual work to be run. Hopefully we can stop using MessageLoop and this
// interface will become more concise.
class BASE_EXPORT ThreadController {
public:
// Phases the top-RunLevel can go through. While these are more precise than
// RunLevelTracker::State, unlike it: phases are determined retrospectively
// as we often only find out the type of work that was just performed at the
// end of a phase. Or even find out about past phases later in the timeline
// (i.e. kScheduled is only known after the first kSelectingApplicationTask
// phase out-of-idle).
// Public for unit tests.
// These values are logged to UMA. Entries should not be renumbered and
// numeric values should never be reused. Please keep in sync
// with "MessagePumpPhases" in src/tools/metrics/histograms/enums.xml.
enum Phase {
kScheduled = 1,
kPumpOverhead = 2,
// Any work item, in practice application tasks are mapped to
// kApplicationTask so this only accounts for native work.
kWorkItem = 3,
kNativeWork = kWorkItem,
kSelectingApplicationTask = 4,
kApplicationTask = 5,
kIdleWork = 6,
kNested = 7,
kLastPhase = kNested,
// Reported as a kWorkItem but doesn't clear state relevant to the ongoing
// work item as it isn't finished (will resume after nesting).
kWorkItemSuspendedOnNested,
};
explicit ThreadController(const TickClock* time_source);
virtual ~ThreadController();
// Sets the number of tasks executed in a single invocation of DoWork.
// Increasing the batch size can reduce the overhead of yielding back to the
// main message loop.
virtual void SetWorkBatchSize(int work_batch_size = 1) = 0;
// Notifies that |pending_task| is about to be enqueued. Needed for tracing
// purposes. The impl may use this opportunity add metadata to |pending_task|
// before it is moved into the queue.
virtual void WillQueueTask(PendingTask* pending_task) = 0;
// Notify the controller that its associated sequence has immediate work
// to run. Shortly after this is called, the thread associated with this
// controller will run a task returned by sequence->TakeTask(). Can be called
// from any sequence.
//
// TODO(altimin): Change this to "the thread associated with this
// controller will run tasks returned by sequence->TakeTask() until it
// returns null or sequence->DidRunTask() returns false" once the
// code is changed to work that way.
virtual void ScheduleWork() = 0;
// Notify the controller that SequencedTaskSource will have a delayed work
// ready to be run at |wake_up|. This call cancels any previously
// scheduled delayed work. Can only be called from the main sequence.
// NOTE: GetPendingWakeUp might return a different value as it also takes
// immediate work into account.
// TODO(kraynov): Remove |lazy_now| parameter.
virtual void SetNextDelayedDoWork(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) = 0;
// Sets the sequenced task source from which to take tasks after
// a Schedule*Work() call is made.
// Must be called before the first call to Schedule*Work().
virtual void SetSequencedTaskSource(SequencedTaskSource*) = 0;
// Completes delayed initialization of unbound ThreadControllers.
// BindToCurrentThread(MessageLoopBase*) or BindToCurrentThread(MessagePump*)
// may only be called once.
virtual void BindToCurrentThread(
std::unique_ptr<MessagePump> message_pump) = 0;
// Explicitly allow or disallow task execution. Implicitly disallowed when
// entering a nested runloop.
virtual void SetTaskExecutionAllowedInNativeNestedLoop(bool allowed) = 0;
// Whether task execution is allowed or not.
virtual bool IsTaskExecutionAllowed() const = 0;
// Returns the MessagePump we're bound to if any.
virtual MessagePump* GetBoundMessagePump() const = 0;
// Returns true if the current run loop should quit when idle.
virtual bool ShouldQuitRunLoopWhenIdle() = 0;
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
// On iOS, the main message loop cannot be Run(). Instead call
// AttachToMessagePump(), which connects this ThreadController to the
// UI thread's CFRunLoop and allows PostTask() to work.
virtual void AttachToMessagePump() = 0;
#endif
#if BUILDFLAG(IS_IOS)
// Detaches this ThreadController from the message pump, allowing the
// controller to be shut down cleanly.
virtual void DetachFromMessagePump() = 0;
#endif
// Initializes features for this class. See `base::features::Init()`.
static void InitializeFeatures(
features::EmitThreadControllerProfilerMetadata emit_profiler_metadata);
// Enables TimeKeeper metrics. `thread_name` will be used as a suffix.
// Setting `wall_time_based_metrics_enabled_for_testing` adds wall-time
// based metrics for this thread. It also also disables subsampling.
void EnableMessagePumpTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing);
// Currently only overridden on ThreadControllerWithMessagePumpImpl.
//
// While Now() is less than |prioritize_until| we will alternate between
// |work_batch_size| tasks before setting |yield_to_native| on the
// NextWorkInfo and yielding to the underlying sequence (e.g. the message
// pump).
virtual void PrioritizeYieldingToNative(base::TimeTicks prioritize_until) = 0;
// Sets the SingleThreadTaskRunner that will be returned by
// SingleThreadTaskRunner::GetCurrentDefault on the thread controlled by this
// ThreadController.
virtual void SetDefaultTaskRunner(scoped_refptr<SingleThreadTaskRunner>) = 0;
// TODO(altimin): Get rid of the methods below.
// These methods exist due to current integration of SequenceManager
// with MessageLoop.
virtual bool RunsTasksInCurrentSequence() = 0;
void SetTickClock(const TickClock* clock);
virtual scoped_refptr<SingleThreadTaskRunner> GetDefaultTaskRunner() = 0;
virtual void RestoreDefaultTaskRunner() = 0;
virtual void AddNestingObserver(RunLoop::NestingObserver* observer) = 0;
virtual void RemoveNestingObserver(RunLoop::NestingObserver* observer) = 0;
const scoped_refptr<AssociatedThreadId>& GetAssociatedThread() const {
return associated_thread_;
}
protected:
const scoped_refptr<AssociatedThreadId> associated_thread_;
// The source of TimeTicks for this ThreadController.
// Must only be accessed from the `associated_thread_`.
// TODO(scheduler-dev): This could be made
// `GUARDED_BY_CONTEXT(associated_thread_->thread_checker)` when
// switching MainThreadOnly to thread annotations and annotating all
// thread-affine ThreadController methods. Without that, this lone annotation
// would result in an inconsistent set of DCHECKs...
raw_ptr<const TickClock> time_source_; // Not owned.
// Whether or not wall-time based metrics are enabled.
bool wall_time_based_metrics_enabled_for_testing_;
// Tracks the state of each run-level (main and nested ones) in its associated
// ThreadController. It does so using two high-level principles:
// 1) #work-in-work-implies-nested :
// If the |state_| is kRunningWorkItem and another work item starts
// (OnWorkStarted()), it implies this inner-work-item is running from a
// 2) #done-work-at-lower-runlevel-implies-done-nested
// WorkItems are required to pass in the nesting depth at which they were
// created in OnWorkEnded(). Then, if |rundepth| is lower than the current
// RunDepth(), we know the top RunLevel was an (already exited) nested
// loop and will be popped off |run_levels_|.
// We need this logic because native nested loops can run from any work item
// without a RunLoop being involved, see
// ThreadControllerWithMessagePumpTest.ThreadControllerActive* tests for
// examples. Using these two heuristics is the simplest way, trying to
// capture all the ways in which work items can nest is harder than reacting
// as it happens.
//
// Note 1: "native work" is only captured if the MessagePump is
// instrumented to see them and shares them with ThreadController (via
// MessagePump::Delegate::OnBeginWorkItem). As such it is still possible to
// view trace events emanating from native work without "ThreadController
// active" being active.
// Note 2: Non-instrumented native work does not break the two high-level
// principles above because:
// A) If a non-instrumented work item enters a nested loop, either:
// i) No instrumented work run within the loop so it's invisible.
// ii) Instrumented work runs *and* current state is kRunningWorkItem
// ((A) is a work item within an instrumented work item):
// #work-in-work-implies-nested triggers and the nested loop is
// visible.
// iii) Instrumented work runs *and* current state is kIdle or
// kInBetweenWorkItems ((A) is a work item run by a native loop):
// #work-in-work-implies-nested doesn't trigger and this instrumented
// work (iii) looks like a non-nested continuation of work at the
// current RunLevel.
// B) When work item (A) exits its nested loop and completes, respectively:
// i) The loop was invisible so no RunLevel was created for it and
// #done-work-at-lower-runlevel-implies-done-nested doesn't trigger so
// it balances out.
// ii) Instrumented work did run, and so RunLevels() increased. However,
// since instrumented work (the work which called the nested loop)
// keeps track of its own run depth, on its exit, we know to pop the
// RunLevel corresponding to the nested work.
// iii) Nested instrumented work was visible but didn't appear nested,
// state is now back to kInBetweenWorkItems or kIdle as before (A).
class BASE_EXPORT RunLevelTracker {
public:
// States each RunLevel can be in.
enum State {
// Waiting for work (pending wakeup).
kIdle,
// Between two work items but not idle.
kInBetweenWorkItems,
// Running and currently processing a work items (includes selecting the
// next work item, i.e. either peeking the native work queue or selecting
// the next application task).
kRunningWorkItem,
};
explicit RunLevelTracker(const ThreadController& outer);
~RunLevelTracker();
void OnRunLoopStarted(State initial_state, LazyNow& lazy_now);
void OnRunLoopEnded();
void OnWorkStarted(LazyNow& lazy_now);
void OnApplicationTaskSelected(TimeTicks queue_time, LazyNow& lazy_now);
void OnWorkEnded(LazyNow& lazy_now, int run_level_depth);
void OnIdle(LazyNow& lazy_now);
size_t num_run_levels() const {
DCHECK_CALLED_ON_VALID_THREAD(outer_->associated_thread_->thread_checker);
return run_levels_.size();
}
// Emits a perfetto::Flow (wakeup.flow) event associated with this
// RunLevelTracker.
void RecordScheduleWork();
void EnableTimeKeeperMetrics(
const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing);
// Observes changes of state sent as trace-events so they can be tested.
class TraceObserverForTesting {
public:
virtual ~TraceObserverForTesting() = default;
virtual void OnThreadControllerActiveBegin() = 0;
virtual void OnThreadControllerActiveEnd() = 0;
virtual void OnPhaseRecorded(Phase phase) = 0;
};
static void SetTraceObserverForTesting(
TraceObserverForTesting* trace_observer_for_testing);
private:
// Keeps track of the time spent in various Phases (ignores idle), reports
// via UMA to the corresponding phase every time one reaches >= 100ms of
// cumulative time, resulting in a metric of relative time spent in each
// non-idle phase. Also emits each phase as a trace event on its own
// MessagePumpPhases track when the disabled-by-default-base tracing
// category is enabled.
class TimeKeeper {
public:
explicit TimeKeeper(const RunLevelTracker& outer);
void EnableRecording(const char* thread_name,
bool wall_time_based_metrics_enabled_for_testing);
// Records the start time of the first phase out-of-idle. The kScheduled
// phase will be attributed the time before this point once its
// `queue_time` is known.
void RecordWakeUp(LazyNow& lazy_now);
// Accounts the time since OnWorkStarted() towards
// kSelectingApplicationTask. Accounts `queue_time - last_wakeup_` towards
// kScheduled (iff `queue_time` is not null nor later than
// `last_wakeup_`). And flags the current kWorkItem as a kApplicationTask,
// to be accounted from OnWorkEnded(). Emits a trace event for the
// kScheduled phase if applicable.
void OnApplicationTaskSelected(TimeTicks queue_time, LazyNow& lazy_now);
// If recording is enabled: Records the end of a phase, attributing it the
// delta between `lazy_now` and `last_phase_end` and emit a trace event
// for it.
void RecordEndOfPhase(Phase phase, LazyNow& lazy_now);
// If recording is enabled: If the `wakeup.flow` category is enabled,
// record a TerminatingFlow into the current "ThreadController Active"
// track event.
void MaybeEmitIncomingWakeupFlow(perfetto::EventContext& ctx);
const std::string& thread_name() const LIFETIME_BOUND {
return thread_name_;
}
bool wall_time_based_metrics_enabled_for_testing() const {
return wall_time_based_metrics_enabled_for_testing_;
}
private:
enum class ShouldRecordReqs {
// Regular should-record requirements.
kRegular,
// On wakeup there's an exception to the requirement that `last_wakeup_`
// be set.
kOnWakeUp,
// On end-nested there's an exception to the requirement that there's no
// ongoing nesting (as the kNested phase ends from ~RunLevel, before
// run_levels.pop() completes).
kOnEndNested,
};
bool ShouldRecordNow(ShouldRecordReqs reqs = ShouldRecordReqs::kRegular);
// Common helper to actually record time in a phase and emitt histograms
// as needed.
void RecordTimeInPhase(Phase phase,
TimeTicks phase_begin,
TimeTicks phase_end);
static const char* PhaseToEventName(Phase phase);
std::string thread_name_;
// Whether or not wall-time based metrics are reported.
bool wall_time_based_metrics_enabled_for_testing_ = false;
// Cumulative time deltas for each phase, reported and reset when >=100ms.
std::array<TimeDelta, Phase::kLastPhase + 1> deltas_ = {};
// Set at the start of the first work item out-of-idle. Consumed from the
// first application task found in that work cycle
// (in OnApplicationTaskSelected).
TimeTicks last_wakeup_;
// The end of the last phase (used as the beginning of the next one).
TimeTicks last_phase_end_;
// The end of the last kIdleWork phase. Used as a minimum for the next
// kScheduled phase's begin (as it's possible that the next wake-up is
// scheduled during DoIdleWork and we don't want overlapping phases).
TimeTicks last_sleep_;
// Assumes each kWorkItem is native unless OnApplicationTaskSelected() is
// invoked in a given [OnWorkStarted, OnWorkEnded].
bool current_work_item_is_native_ = true;
// non-null when recording is enabled.
raw_ptr<HistogramBase> histogram_ = nullptr;
#if BUILDFLAG(ENABLE_BASE_TRACING)
std::optional<perfetto::Track> perfetto_track_;
// True if tracing was enabled during the last pass of RecordTimeInPhase.
bool was_tracing_enabled_ = false;
#endif
const raw_ref<const RunLevelTracker> outer_;
} time_keeper_{*this};
class RunLevel {
public:
RunLevel(State initial_state,
bool is_nested,
TimeKeeper& time_keeper,
LazyNow& lazy_now);
~RunLevel();
// Move-constructible for STL compat. Flags `other.was_moved_` so it noops
// on destruction after handing off its responsibility. Move-assignment
// is not necessary nor possible as not all members are assignable.
RunLevel(RunLevel&& other);
RunLevel& operator=(RunLevel&&) = delete;
void UpdateState(State new_state, LazyNow& lazy_now);
State state() const { return state_; }
void set_exit_lazy_now(LazyNow* exit_lazy_now) {
DCHECK(exit_lazy_now);
DCHECK(!exit_lazy_now_);
exit_lazy_now_ = exit_lazy_now;
}
private:
void LogPercentageMetric(const char* name, int value);
void LogPercentageMetric(const char* name,
int value,
base::TimeDelta interval_duration);
void LogIntervalMetric(const char* name,
base::TimeDelta value,
base::TimeDelta interval_duration);
void LogOnActiveMetrics(LazyNow& lazy_now);
void LogOnIdleMetrics(LazyNow& lazy_now);
base::TimeTicks last_active_end_;
base::TimeTicks last_active_start_;
base::ThreadTicks last_active_threadtick_start_;
base::TimeDelta accumulated_idle_time_;
base::TimeDelta accumulated_active_time_;
base::TimeDelta accumulated_active_on_cpu_time_;
base::TimeDelta accumulated_active_off_cpu_time_;
MetricsSubSampler metrics_sub_sampler_;
State state_ = kIdle;
bool is_nested_;
bool ShouldRecordSampleMetadata();
// Get full suffix for histogram logging purposes. |duration| should equal
// TimeDelta() when not applicable.
std::string GetSuffixForHistogram(TimeDelta duration);
std::string GetSuffixForCatchAllHistogram();
std::string_view GetThreadName();
const raw_ref<TimeKeeper> time_keeper_;
// Must be set shortly before ~RunLevel.
raw_ptr<LazyNow> exit_lazy_now_ = nullptr;
SampleMetadata thread_controller_sample_metadata_;
size_t thread_controller_active_id_ = 0;
// Toggles to true when used as RunLevel&& input to construct another
// RunLevel. This RunLevel's destructor will then no-op.
class TruePostMove {
public:
TruePostMove() = default;
TruePostMove(TruePostMove&& other) { other.was_moved_ = true; }
// Not necessary for now.
TruePostMove& operator=(TruePostMove&&) = delete;
explicit operator bool() { return was_moved_; }
private:
bool was_moved_ = false;
};
TruePostMove was_moved_;
};
[[maybe_unused]] const raw_ref<const ThreadController> outer_;
std::stack<RunLevel, std::vector<RunLevel>> run_levels_
GUARDED_BY_CONTEXT(outer_->associated_thread_->thread_checker);
static TraceObserverForTesting* trace_observer_for_testing_;
} run_level_tracker_{*this};
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_

View File

@@ -0,0 +1,376 @@
// Copyright 2017 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/thread_controller_impl.h"
#include <algorithm>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_pump.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/sequenced_task_source.h"
#include "base/trace_event/base_tracing.h"
#include "build/build_config.h"
namespace base {
namespace sequence_manager {
namespace internal {
using ShouldScheduleWork = WorkDeduplicator::ShouldScheduleWork;
ThreadControllerImpl::ThreadControllerImpl(
SequenceManagerImpl* funneled_sequence_manager,
scoped_refptr<SingleThreadTaskRunner> task_runner,
const TickClock* time_source)
: ThreadController(time_source),
funneled_sequence_manager_(funneled_sequence_manager),
task_runner_(task_runner),
message_loop_task_runner_(funneled_sequence_manager
? funneled_sequence_manager->GetTaskRunner()
: nullptr),
work_deduplicator_(associated_thread_) {
if (task_runner_ || funneled_sequence_manager_)
work_deduplicator_.BindToCurrentThread();
immediate_do_work_closure_ =
BindRepeating(&ThreadControllerImpl::DoWork, weak_factory_.GetWeakPtr(),
WorkType::kImmediate);
delayed_do_work_closure_ =
BindRepeating(&ThreadControllerImpl::DoWork, weak_factory_.GetWeakPtr(),
WorkType::kDelayed);
// Unlike ThreadControllerWithMessagePumpImpl, ThreadControllerImpl isn't
// explicitly Run(). Rather, DoWork() will be invoked at some point in the
// future when the associated thread begins pumping messages.
LazyNow lazy_now(time_source_);
run_level_tracker_.OnRunLoopStarted(RunLevelTracker::kIdle, lazy_now);
}
ThreadControllerImpl::~ThreadControllerImpl() {
// Balances OnRunLoopStarted() in the constructor to satisfy the exit criteria
// of ~RunLevelTracker().
run_level_tracker_.OnRunLoopEnded();
}
ThreadControllerImpl::MainSequenceOnly::MainSequenceOnly() = default;
ThreadControllerImpl::MainSequenceOnly::~MainSequenceOnly() = default;
std::unique_ptr<ThreadControllerImpl> ThreadControllerImpl::Create(
SequenceManagerImpl* funneled_sequence_manager,
const TickClock* time_source) {
return WrapUnique(new ThreadControllerImpl(
funneled_sequence_manager,
funneled_sequence_manager ? funneled_sequence_manager->GetTaskRunner()
: nullptr,
time_source));
}
void ThreadControllerImpl::SetSequencedTaskSource(
SequencedTaskSource* sequence) {
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
DCHECK(sequence);
DCHECK(!sequence_);
sequence_ = sequence;
}
void ThreadControllerImpl::ScheduleWork() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
"ThreadControllerImpl::ScheduleWork::PostTask");
if (work_deduplicator_.OnWorkRequested() ==
ShouldScheduleWork::kScheduleImmediate) {
task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
}
}
void ThreadControllerImpl::SetNextDelayedDoWork(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) {
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
DCHECK(sequence_);
DCHECK(!wake_up || !wake_up->is_immediate());
// Cancel DoWork if it was scheduled and we set an "infinite" delay now.
if (!wake_up) {
if (!main_sequence_only().next_delayed_do_work.is_max()) {
cancelable_delayed_do_work_closure_.Cancel();
main_sequence_only().next_delayed_do_work = TimeTicks::Max();
}
return;
}
if (work_deduplicator_.OnDelayedWorkRequested() ==
ShouldScheduleWork::kNotNeeded) {
return;
}
if (main_sequence_only().next_delayed_do_work == wake_up->time)
return;
base::TimeDelta delay =
std::max(TimeDelta(), wake_up->time - lazy_now->Now());
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
"ThreadControllerImpl::SetNextDelayedDoWork::PostDelayedTask",
"delay_ms", delay.InMillisecondsF());
main_sequence_only().next_delayed_do_work = wake_up->time;
// Reset also causes cancellation of the previous DoWork task.
cancelable_delayed_do_work_closure_.Reset(delayed_do_work_closure_);
task_runner_->PostDelayedTask(
FROM_HERE, cancelable_delayed_do_work_closure_.callback(), delay);
}
bool ThreadControllerImpl::RunsTasksInCurrentSequence() {
return task_runner_->RunsTasksInCurrentSequence();
}
void ThreadControllerImpl::SetDefaultTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) {
#if DCHECK_IS_ON()
default_task_runner_set_ = true;
#endif
if (!funneled_sequence_manager_)
return;
funneled_sequence_manager_->SetTaskRunner(task_runner);
}
scoped_refptr<SingleThreadTaskRunner>
ThreadControllerImpl::GetDefaultTaskRunner() {
return funneled_sequence_manager_->GetTaskRunner();
}
void ThreadControllerImpl::RestoreDefaultTaskRunner() {
if (!funneled_sequence_manager_)
return;
funneled_sequence_manager_->SetTaskRunner(message_loop_task_runner_);
}
void ThreadControllerImpl::BindToCurrentThread(
std::unique_ptr<MessagePump> message_pump) {
NOTREACHED();
}
void ThreadControllerImpl::WillQueueTask(PendingTask* pending_task) {
task_annotator_.WillQueueTask("SequenceManager PostTask", pending_task);
}
void ThreadControllerImpl::DoWork(WorkType work_type) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
"ThreadControllerImpl::DoWork");
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
DCHECK(sequence_);
work_deduplicator_.OnWorkStarted();
std::optional<base::TimeTicks> recent_time;
WeakPtr<ThreadControllerImpl> weak_ptr = weak_factory_.GetWeakPtr();
for (int i = 0; i < main_sequence_only().work_batch_size_; i++) {
LazyNow lazy_now_select_task(recent_time, time_source_);
// Include SelectNextTask() in the scope of the work item. This ensures
// it's covered in tracing and hang reports. This is particularly
// important when SelectNextTask() finds no work immediately after a
// wakeup, otherwise the power-inefficient wakeup is invisible in
// tracing. OnApplicationTaskSelected() assumes this ordering as well.
DCHECK_GT(run_level_tracker_.num_run_levels(), 0U);
run_level_tracker_.OnWorkStarted(lazy_now_select_task);
int run_depth = static_cast<int>(run_level_tracker_.num_run_levels());
std::optional<SequencedTaskSource::SelectedTask> selected_task =
sequence_->SelectNextTask(lazy_now_select_task);
LazyNow lazy_now_task_selected(time_source_);
run_level_tracker_.OnApplicationTaskSelected(
(selected_task && selected_task->task.delayed_run_time.is_null())
? selected_task->task.queue_time
: TimeTicks(),
lazy_now_task_selected);
if (!selected_task) {
run_level_tracker_.OnWorkEnded(lazy_now_task_selected, run_depth);
break;
}
{
// Trace-parsing tools (DevTools, Lighthouse, etc) consume this event
// to determine long tasks.
// See https://crbug.com/681863 and https://crbug.com/874982
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "RunTask");
// Note: all arguments after task are just passed to a TRACE_EVENT for
// logging so lambda captures are safe as lambda is executed inline.
SequencedTaskSource* source = sequence_;
task_annotator_.RunTask(
"ThreadControllerImpl::RunTask", selected_task->task,
[&selected_task, &source](perfetto::EventContext& ctx) {
if (selected_task->task_execution_trace_logger)
selected_task->task_execution_trace_logger.Run(
ctx, selected_task->task);
source->MaybeEmitTaskDetails(ctx, *selected_task);
});
if (!weak_ptr)
return;
// This processes microtasks, hence all scoped operations above must end
// after it.
LazyNow lazy_now_after_run_task(time_source_);
sequence_->DidRunTask(lazy_now_after_run_task);
run_level_tracker_.OnWorkEnded(lazy_now_after_run_task, run_depth);
// If DidRunTask() read the clock (lazy_now_after_run_task.has_value()),
// store it in `recent_time` so it can be reused by SelectNextTask() at
// the next loop iteration.
if (lazy_now_after_run_task.has_value()) {
recent_time =
std::optional<base::TimeTicks>(lazy_now_after_run_task.Now());
} else {
recent_time.reset();
}
}
// NOTE: https://crbug.com/828835.
// When we're running inside a nested RunLoop it may quit anytime, so any
// outstanding pending tasks must run in the outer RunLoop
// (see SequenceManagerTestWithMessageLoop.QuitWhileNested test).
// Unfortunately, it's MessageLoop who's receiving that signal and we can't
// know it before we return from DoWork, hence, OnExitNestedRunLoop
// will be called later. Since we must implement ThreadController and
// SequenceManager in conformance with MessageLoop task runners, we need
// to disable this batching optimization while nested.
// Implementing MessagePump::Delegate ourselves will help to resolve this
// issue.
if (run_level_tracker_.num_run_levels() > 1)
break;
}
work_deduplicator_.WillCheckForMoreWork();
LazyNow lazy_now_after_work(time_source_);
std::optional<WakeUp> next_wake_up =
sequence_->GetPendingWakeUp(&lazy_now_after_work);
// The OnIdle() callback allows the TimeDomains to advance virtual time in
// which case we now have immediate work to do.
if ((next_wake_up && next_wake_up->is_immediate()) || sequence_->OnIdle()) {
// The next task needs to run immediately, post a continuation if
// another thread didn't get there first.
if (work_deduplicator_.DidCheckForMoreWork(
WorkDeduplicator::NextTask::kIsImmediate) ==
ShouldScheduleWork::kScheduleImmediate) {
task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
}
return;
}
// It looks like we have a non-zero delay, however another thread may have
// posted an immediate task while we computed the delay.
if (work_deduplicator_.DidCheckForMoreWork(
WorkDeduplicator::NextTask::kIsDelayed) ==
ShouldScheduleWork::kScheduleImmediate) {
task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
return;
}
// No more immediate work.
run_level_tracker_.OnIdle(lazy_now_after_work);
// Any future work?
if (!next_wake_up) {
main_sequence_only().next_delayed_do_work = TimeTicks::Max();
cancelable_delayed_do_work_closure_.Cancel();
return;
}
TimeTicks next_wake_up_time = next_wake_up->time;
// Already requested next delay?
if (next_wake_up_time == main_sequence_only().next_delayed_do_work)
return;
// Schedule a callback after |delay_till_next_task| and cancel any previous
// callback.
main_sequence_only().next_delayed_do_work = next_wake_up_time;
cancelable_delayed_do_work_closure_.Reset(delayed_do_work_closure_);
// TODO(crbug.com/40158967): Use PostDelayedTaskAt().
task_runner_->PostDelayedTask(FROM_HERE,
cancelable_delayed_do_work_closure_.callback(),
next_wake_up_time - lazy_now_after_work.Now());
}
void ThreadControllerImpl::AddNestingObserver(
RunLoop::NestingObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
nesting_observer_ = observer;
RunLoop::AddNestingObserverOnCurrentThread(this);
}
void ThreadControllerImpl::RemoveNestingObserver(
RunLoop::NestingObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
DCHECK_EQ(observer, nesting_observer_);
nesting_observer_ = nullptr;
RunLoop::RemoveNestingObserverOnCurrentThread(this);
}
void ThreadControllerImpl::OnBeginNestedRunLoop() {
LazyNow lazy_now(time_source_);
run_level_tracker_.OnRunLoopStarted(RunLevelTracker::kInBetweenWorkItems,
lazy_now);
// Just assume we have a pending task and post a DoWork to make sure we don't
// grind to a halt while nested.
work_deduplicator_.OnWorkRequested(); // Set the pending DoWork flag.
task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
if (nesting_observer_)
nesting_observer_->OnBeginNestedRunLoop();
}
void ThreadControllerImpl::OnExitNestedRunLoop() {
if (nesting_observer_)
nesting_observer_->OnExitNestedRunLoop();
run_level_tracker_.OnRunLoopEnded();
}
void ThreadControllerImpl::SetWorkBatchSize(int work_batch_size) {
main_sequence_only().work_batch_size_ = work_batch_size;
}
void ThreadControllerImpl::SetTaskExecutionAllowedInNativeNestedLoop(
bool allowed) {
NOTREACHED();
}
bool ThreadControllerImpl::IsTaskExecutionAllowed() const {
return true;
}
bool ThreadControllerImpl::ShouldQuitRunLoopWhenIdle() {
// The MessageLoop does not expose the API needed to support this query.
return false;
}
MessagePump* ThreadControllerImpl::GetBoundMessagePump() const {
return nullptr;
}
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
void ThreadControllerImpl::AttachToMessagePump() {
NOTREACHED();
}
#endif // BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_IOS)
void ThreadControllerImpl::DetachFromMessagePump() {
NOTREACHED();
}
#endif // BUILDFLAG(IS_IOS)
void ThreadControllerImpl::PrioritizeYieldingToNative(base::TimeTicks) {
NOTREACHED();
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,132 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_IMPL_H_
#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_IMPL_H_
#include <memory>
#include "base/base_export.h"
#include "base/cancelable_callback.h"
#include "base/compiler_specific.h"
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/task/common/task_annotator.h"
#include "base/task/sequence_manager/thread_controller.h"
#include "base/task/sequence_manager/work_deduplicator.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
namespace base {
namespace sequence_manager {
namespace internal {
class SequenceManagerImpl;
// This is the interface between a SequenceManager which sits on top of an
// underlying SequenceManagerImpl or SingleThreadTaskRunner. Currently it's only
// used for workers in blink although we'd intend to migrate those to
// ThreadControllerWithMessagePumpImpl (https://crbug.com/948051). Long term we
// intend to use this for sequence funneling.
class BASE_EXPORT ThreadControllerImpl : public ThreadController,
public RunLoop::NestingObserver {
public:
ThreadControllerImpl(const ThreadControllerImpl&) = delete;
ThreadControllerImpl& operator=(const ThreadControllerImpl&) = delete;
~ThreadControllerImpl() override;
// TODO(crbug.com/40620995): replace |funneled_sequence_manager| with
// |funneled_task_runner| when we sort out the workers
static std::unique_ptr<ThreadControllerImpl> Create(
SequenceManagerImpl* funneled_sequence_manager,
const TickClock* time_source);
// ThreadController:
void SetWorkBatchSize(int work_batch_size) override;
void WillQueueTask(PendingTask* pending_task) override;
void ScheduleWork() override;
void BindToCurrentThread(std::unique_ptr<MessagePump> message_pump) override;
void SetNextDelayedDoWork(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) override;
void SetSequencedTaskSource(SequencedTaskSource* sequence) override;
bool RunsTasksInCurrentSequence() override;
void SetDefaultTaskRunner(scoped_refptr<SingleThreadTaskRunner>) override;
scoped_refptr<SingleThreadTaskRunner> GetDefaultTaskRunner() override;
void RestoreDefaultTaskRunner() override;
void AddNestingObserver(RunLoop::NestingObserver* observer) override;
void RemoveNestingObserver(RunLoop::NestingObserver* observer) override;
void SetTaskExecutionAllowedInNativeNestedLoop(bool allowed) override;
bool IsTaskExecutionAllowed() const override;
MessagePump* GetBoundMessagePump() const override;
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
void AttachToMessagePump() override;
#endif
#if BUILDFLAG(IS_IOS)
void DetachFromMessagePump() override;
#endif
void PrioritizeYieldingToNative(base::TimeTicks prioritize_until) override;
bool ShouldQuitRunLoopWhenIdle() override;
// RunLoop::NestingObserver:
void OnBeginNestedRunLoop() override;
void OnExitNestedRunLoop() override;
protected:
ThreadControllerImpl(SequenceManagerImpl* sequence_manager,
scoped_refptr<SingleThreadTaskRunner> task_runner,
const TickClock* time_source);
const raw_ptr<SequenceManagerImpl> funneled_sequence_manager_;
const scoped_refptr<SingleThreadTaskRunner> task_runner_;
raw_ptr<RunLoop::NestingObserver> nesting_observer_ = nullptr;
private:
enum class WorkType { kImmediate, kDelayed };
void DoWork(WorkType work_type);
// TODO(scheduler-dev): Maybe fold this into the main class and use
// thread annotations.
struct MainSequenceOnly {
MainSequenceOnly();
~MainSequenceOnly();
int work_batch_size_ = 1;
TimeTicks next_delayed_do_work = TimeTicks::Max();
};
MainSequenceOnly main_sequence_only_;
MainSequenceOnly& main_sequence_only() LIFETIME_BOUND {
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
return main_sequence_only_;
}
const MainSequenceOnly& main_sequence_only() const LIFETIME_BOUND {
DCHECK_CALLED_ON_VALID_SEQUENCE(associated_thread_->sequence_checker);
return main_sequence_only_;
}
scoped_refptr<SingleThreadTaskRunner> message_loop_task_runner_;
RepeatingClosure immediate_do_work_closure_;
RepeatingClosure delayed_do_work_closure_;
CancelableRepeatingClosure cancelable_delayed_do_work_closure_;
raw_ptr<SequencedTaskSource> sequence_ = nullptr; // Not owned.
TaskAnnotator task_annotator_;
WorkDeduplicator work_deduplicator_;
#if DCHECK_IS_ON()
bool default_task_runner_set_ = false;
#endif
WeakPtrFactory<ThreadControllerImpl> weak_factory_{this};
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_IMPL_H_

View File

@@ -0,0 +1,96 @@
// Copyright 2020 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/thread_controller_power_monitor.h"
#include "base/feature_list.h"
#include "base/power_monitor/power_monitor.h"
#include "base/trace_event/base_tracing.h"
namespace base {
namespace sequence_manager {
namespace internal {
namespace {
// Activate the power management events that affect task scheduling.
BASE_FEATURE(kUsePowerMonitorWithThreadController,
"UsePowerMonitorWithThreadController",
FEATURE_ENABLED_BY_DEFAULT);
// TODO(crbug.com/40127966): Remove this when the experiment becomes the
// default.
bool g_use_thread_controller_power_monitor_ = false;
} // namespace
ThreadControllerPowerMonitor::ThreadControllerPowerMonitor() = default;
ThreadControllerPowerMonitor::~ThreadControllerPowerMonitor() {
PowerMonitor::GetInstance()->RemovePowerSuspendObserver(this);
}
void ThreadControllerPowerMonitor::BindToCurrentThread() {
// Occasionally registration happens twice (i.e. when the
// ThreadController::SetDefaultTaskRunner() re-initializes the
// ThreadController).
auto* power_monitor = PowerMonitor::GetInstance();
if (is_observer_registered_)
power_monitor->RemovePowerSuspendObserver(this);
// Register the observer to deliver notifications on the current thread.
power_monitor->AddPowerSuspendObserver(this);
is_observer_registered_ = true;
}
bool ThreadControllerPowerMonitor::IsProcessInPowerSuspendState() {
return is_power_suspended_;
}
// static
void ThreadControllerPowerMonitor::InitializeFeatures() {
DCHECK(!g_use_thread_controller_power_monitor_);
g_use_thread_controller_power_monitor_ =
FeatureList::IsEnabled(kUsePowerMonitorWithThreadController);
}
// static
void ThreadControllerPowerMonitor::OverrideUsePowerMonitorForTesting(
bool use_power_monitor) {
g_use_thread_controller_power_monitor_ = use_power_monitor;
}
// static
void ThreadControllerPowerMonitor::ResetForTesting() {
g_use_thread_controller_power_monitor_ = false;
}
void ThreadControllerPowerMonitor::OnSuspend() {
if (!g_use_thread_controller_power_monitor_)
return;
DCHECK(!is_power_suspended_);
TRACE_EVENT_BEGIN("base", "ThreadController::Suspended",
perfetto::Track(reinterpret_cast<uint64_t>(this),
perfetto::ThreadTrack::Current()));
is_power_suspended_ = true;
}
void ThreadControllerPowerMonitor::OnResume() {
if (!g_use_thread_controller_power_monitor_)
return;
// It is possible a suspend was already happening before the observer was
// added to the power monitor. Ignoring the resume notification in that case.
if (is_power_suspended_) {
TRACE_EVENT_END("base" /* ThreadController::Suspended */,
perfetto::Track(reinterpret_cast<uint64_t>(this),
perfetto::ThreadTrack::Current()));
is_power_suspended_ = false;
}
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,56 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_POWER_MONITOR_H_
#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_POWER_MONITOR_H_
#include "base/base_export.h"
#include "base/power_monitor/power_observer.h"
namespace base {
namespace sequence_manager {
namespace internal {
// A helper class that keeps track of the power state and handles power
// notifications. The class register itself to the PowerMonitor and receives
// notifications on the bound thread (see BindToCurrentThread(...)).
class BASE_EXPORT ThreadControllerPowerMonitor : public PowerSuspendObserver {
public:
ThreadControllerPowerMonitor();
~ThreadControllerPowerMonitor() override;
ThreadControllerPowerMonitor(const ThreadControllerPowerMonitor&) = delete;
ThreadControllerPowerMonitor& operator=(const ThreadControllerPowerMonitor&) =
delete;
// Register this class to the power monitor to receive notifications on this
// thread. It is safe to call this before PowerMonitor is initialized.
void BindToCurrentThread();
// Returns whether the process is between power suspend and resume
// notifications.
bool IsProcessInPowerSuspendState();
// Initializes features for this class. See `base::features::Init()`.
static void InitializeFeatures();
static void OverrideUsePowerMonitorForTesting(bool use_power_monitor);
static void ResetForTesting();
// base::PowerSuspendObserver:
void OnSuspend() override;
void OnResume() override;
private:
// Power state based on notifications delivered to this observer.
bool is_power_suspended_ = false;
// Whether PowerMonitor observer is registered.
bool is_observer_registered_ = false;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_POWER_MONITOR_H_

View File

@@ -0,0 +1,756 @@
// Copyright 2018 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/thread_controller_with_message_pump_impl.h"
#include <algorithm>
#include <atomic>
#include <optional>
#include <utility>
#include "base/auto_reset.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/stack_allocated.h"
#include "base/message_loop/message_pump.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/sequence_manager/tasks.h"
#include "base/task/task_features.h"
#include "base/threading/hang_watcher.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/base_tracing.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_IOS)
#include "base/message_loop/message_pump_apple.h"
#elif BUILDFLAG(IS_ANDROID)
#include "base/message_loop/message_pump_android.h"
#endif
namespace base {
namespace sequence_manager {
namespace internal {
namespace {
// Returns |next_run_time| capped at 1 day from |lazy_now|. This is used to
// mitigate https://crbug.com/850450 where some platforms are unhappy with
// delays > 100,000,000 seconds. In practice, a diagnosis metric showed that no
// sleep > 1 hour ever completes (always interrupted by an earlier MessageLoop
// event) and 99% of completed sleeps are the ones scheduled for <= 1 second.
// Details @ https://crrev.com/c/1142589.
TimeTicks CapAtOneDay(TimeTicks next_run_time, LazyNow* lazy_now) {
return std::min(next_run_time, lazy_now->Now() + Days(1));
}
BASE_FEATURE(kAvoidScheduleWorkDuringNativeEventProcessing,
"AvoidScheduleWorkDuringNativeEventProcessing",
base::FEATURE_DISABLED_BY_DEFAULT);
std::atomic_bool g_run_tasks_by_batches = false;
std::atomic_bool g_avoid_schedule_calls_during_native_event_processing = false;
base::TimeDelta GetLeewayForWakeUp(std::optional<WakeUp> wake_up) {
if (!wake_up || wake_up->delay_policy == subtle::DelayPolicy::kPrecise) {
return TimeDelta();
}
return wake_up->leeway;
}
} // namespace
// static
void ThreadControllerWithMessagePumpImpl::InitializeFeatures() {
g_run_tasks_by_batches.store(FeatureList::IsEnabled(base::kRunTasksByBatches),
std::memory_order_relaxed);
g_avoid_schedule_calls_during_native_event_processing.store(
FeatureList::IsEnabled(kAvoidScheduleWorkDuringNativeEventProcessing),
std::memory_order_relaxed);
}
// static
void ThreadControllerWithMessagePumpImpl::ResetFeatures() {
g_run_tasks_by_batches.store(
base::kRunTasksByBatches.default_state == FEATURE_ENABLED_BY_DEFAULT,
std::memory_order_relaxed);
}
ThreadControllerWithMessagePumpImpl::ThreadControllerWithMessagePumpImpl(
const SequenceManager::Settings& settings)
: ThreadController(settings.clock),
work_deduplicator_(associated_thread_),
can_run_tasks_by_batches_(settings.can_run_tasks_by_batches) {}
ThreadControllerWithMessagePumpImpl::ThreadControllerWithMessagePumpImpl(
std::unique_ptr<MessagePump> message_pump,
const SequenceManager::Settings& settings)
: ThreadControllerWithMessagePumpImpl(settings) {
BindToCurrentThread(std::move(message_pump));
}
ThreadControllerWithMessagePumpImpl::~ThreadControllerWithMessagePumpImpl() {
// Destructors of MessagePump::Delegate and
// SingleThreadTaskRunner::CurrentDefaultHandle will do all the clean-up.
// ScopedSetSequenceLocalStorageMapForCurrentThread destructor will
// de-register the current thread as a sequence.
#if BUILDFLAG(IS_WIN)
if (main_thread_only().in_high_res_mode) {
main_thread_only().in_high_res_mode = false;
Time::ActivateHighResolutionTimer(false);
}
#endif
}
// static
std::unique_ptr<ThreadControllerWithMessagePumpImpl>
ThreadControllerWithMessagePumpImpl::CreateUnbound(
const SequenceManager::Settings& settings) {
return base::WrapUnique(new ThreadControllerWithMessagePumpImpl(settings));
}
ThreadControllerWithMessagePumpImpl::MainThreadOnly::MainThreadOnly() = default;
ThreadControllerWithMessagePumpImpl::MainThreadOnly::~MainThreadOnly() =
default;
void ThreadControllerWithMessagePumpImpl::SetSequencedTaskSource(
SequencedTaskSource* task_source) {
DCHECK(task_source);
DCHECK(!main_thread_only().task_source);
main_thread_only().task_source = task_source;
}
void ThreadControllerWithMessagePumpImpl::BindToCurrentThread(
std::unique_ptr<MessagePump> message_pump) {
associated_thread_->BindToCurrentThread();
pump_ = std::move(message_pump);
work_id_provider_ = WorkIdProvider::GetForCurrentThread();
RunLoop::RegisterDelegateForCurrentThread(this);
scoped_set_sequence_local_storage_map_for_current_thread_ = std::make_unique<
base::internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
&sequence_local_storage_map_);
{
base::internal::CheckedAutoLock task_runner_lock(task_runner_lock_);
if (task_runner_)
InitializeSingleThreadTaskRunnerCurrentDefaultHandle();
}
if (work_deduplicator_.BindToCurrentThread() ==
ShouldScheduleWork::kScheduleImmediate) {
pump_->ScheduleWork();
}
}
void ThreadControllerWithMessagePumpImpl::SetWorkBatchSize(
int work_batch_size) {
DCHECK_GE(work_batch_size, 1);
CHECK(main_thread_only().can_change_batch_size);
main_thread_only().work_batch_size = work_batch_size;
}
void ThreadControllerWithMessagePumpImpl::WillQueueTask(
PendingTask* pending_task) {
task_annotator_.WillQueueTask("SequenceManager PostTask", pending_task);
}
void ThreadControllerWithMessagePumpImpl::ScheduleWork() {
base::internal::CheckedLock::AssertNoLockHeldOnCurrentThread();
if (work_deduplicator_.OnWorkRequested() ==
ShouldScheduleWork::kScheduleImmediate) {
if (!associated_thread_->IsBoundToCurrentThread()) {
run_level_tracker_.RecordScheduleWork();
} else {
TRACE_EVENT_INSTANT("wakeup.flow", "ScheduleWorkToSelf");
}
pump_->ScheduleWork();
}
}
void ThreadControllerWithMessagePumpImpl::BeginNativeWorkBeforeDoWork() {
do_work_needed_before_wait_ = true;
if (!g_avoid_schedule_calls_during_native_event_processing.load(
std::memory_order_relaxed)) {
return;
}
// Native nested loops don't guarantee that `DoWork()` will be called after
// executing native work. This is the invariant that is needed to avoid
// calls to `ScheduleWork()`. Since these calls can't be skipped there is
// nothing left to do in this function.
if (task_execution_allowed_in_native_nested_loop_) {
return;
}
// Reuse the deduplicator facility to indicate that there is no need for
// ScheduleWork() until the next time we look for work.
work_deduplicator_.OnWorkStarted();
}
void ThreadControllerWithMessagePumpImpl::SetNextDelayedDoWork(
LazyNow* lazy_now,
std::optional<WakeUp> wake_up) {
DCHECK(!wake_up || !wake_up->is_immediate());
// It's very rare for PostDelayedTask to be called outside of a DoWork in
// production, so most of the time this does nothing.
if (work_deduplicator_.OnDelayedWorkRequested() !=
ShouldScheduleWork::kScheduleImmediate) {
return;
}
TimeTicks run_time =
wake_up.has_value()
? pump_->AdjustDelayedRunTime(wake_up->earliest_time(), wake_up->time,
wake_up->latest_time())
: TimeTicks::Max();
DCHECK_LT(lazy_now->Now(), run_time);
if (!run_time.is_max()) {
run_time = CapAtOneDay(run_time, lazy_now);
}
// |pump_| can't be null as all postTasks are cross-thread before binding,
// and delayed cross-thread postTasks do the thread hop through an immediate
// task.
pump_->ScheduleDelayedWork(
{run_time, GetLeewayForWakeUp(wake_up), lazy_now->Now()});
}
bool ThreadControllerWithMessagePumpImpl::RunsTasksInCurrentSequence() {
return associated_thread_->IsBoundToCurrentThread();
}
void ThreadControllerWithMessagePumpImpl::SetDefaultTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) {
base::internal::CheckedAutoLock lock(task_runner_lock_);
task_runner_ = task_runner;
if (associated_thread_->IsBound()) {
DCHECK(associated_thread_->IsBoundToCurrentThread());
// Thread task runner handle will be created in BindToCurrentThread().
InitializeSingleThreadTaskRunnerCurrentDefaultHandle();
}
}
void ThreadControllerWithMessagePumpImpl::
InitializeSingleThreadTaskRunnerCurrentDefaultHandle() {
// Only one SingleThreadTaskRunner::CurrentDefaultHandle can exist at any
// time, so reset the old one.
main_thread_only().thread_task_runner_handle.reset();
main_thread_only().thread_task_runner_handle =
std::make_unique<SingleThreadTaskRunner::CurrentDefaultHandle>(
task_runner_);
// When the task runner is known, bind the power manager. Power notifications
// are received through that sequence.
power_monitor_.BindToCurrentThread();
}
scoped_refptr<SingleThreadTaskRunner>
ThreadControllerWithMessagePumpImpl::GetDefaultTaskRunner() {
base::internal::CheckedAutoLock lock(task_runner_lock_);
return task_runner_;
}
void ThreadControllerWithMessagePumpImpl::RestoreDefaultTaskRunner() {
// There is no default task runner (as opposed to ThreadControllerImpl).
}
void ThreadControllerWithMessagePumpImpl::AddNestingObserver(
RunLoop::NestingObserver* observer) {
DCHECK(!main_thread_only().nesting_observer);
DCHECK(observer);
main_thread_only().nesting_observer = observer;
RunLoop::AddNestingObserverOnCurrentThread(this);
}
void ThreadControllerWithMessagePumpImpl::RemoveNestingObserver(
RunLoop::NestingObserver* observer) {
DCHECK_EQ(main_thread_only().nesting_observer, observer);
main_thread_only().nesting_observer = nullptr;
RunLoop::RemoveNestingObserverOnCurrentThread(this);
}
void ThreadControllerWithMessagePumpImpl::OnBeginWorkItem() {
LazyNow lazy_now(time_source_);
OnBeginWorkItemImpl(lazy_now);
}
void ThreadControllerWithMessagePumpImpl::OnBeginWorkItemImpl(
LazyNow& lazy_now) {
hang_watch_scope_.emplace();
work_id_provider_->IncrementWorkId();
run_level_tracker_.OnWorkStarted(lazy_now);
main_thread_only().task_source->OnBeginWork();
}
void ThreadControllerWithMessagePumpImpl::OnEndWorkItem(int run_level_depth) {
LazyNow lazy_now(time_source_);
OnEndWorkItemImpl(lazy_now, run_level_depth);
}
void ThreadControllerWithMessagePumpImpl::OnEndWorkItemImpl(
LazyNow& lazy_now,
int run_level_depth) {
// Work completed, begin a new hang watch until the next task (watching the
// pump's overhead).
hang_watch_scope_.emplace();
work_id_provider_->IncrementWorkId();
run_level_tracker_.OnWorkEnded(lazy_now, run_level_depth);
}
void ThreadControllerWithMessagePumpImpl::BeforeWait() {
// DoWork is guaranteed to be called after native work batches and before
// wait.
CHECK(!do_work_needed_before_wait_);
// In most cases, DoIdleWork() will already have cleared the
// `hang_watch_scope_` but in some cases where the native side of the
// MessagePump impl is instrumented, it's possible to get a BeforeWait()
// outside of a DoWork cycle (e.g. message_pump_win.cc :
// MessagePumpForUI::HandleWorkMessage).
hang_watch_scope_.reset();
work_id_provider_->IncrementWorkId();
LazyNow lazy_now(time_source_);
run_level_tracker_.OnIdle(lazy_now);
}
MessagePump::Delegate::NextWorkInfo
ThreadControllerWithMessagePumpImpl::DoWork() {
#if BUILDFLAG(IS_WIN)
// We've been already in a wakeup here. Deactivate the high res timer of OS
// immediately instead of waiting for next DoIdleWork().
if (main_thread_only().in_high_res_mode) {
main_thread_only().in_high_res_mode = false;
Time::ActivateHighResolutionTimer(false);
}
#endif
MessagePump::Delegate::NextWorkInfo next_work_info{};
work_deduplicator_.OnWorkStarted();
LazyNow continuation_lazy_now(time_source_);
std::optional<WakeUp> next_wake_up = DoWorkImpl(&continuation_lazy_now);
// If we are yielding after DoWorkImpl (a work batch) set the flag boolean.
// This will inform the MessagePump to schedule a new continuation based on
// the information below, but even if its immediate let the native sequence
// have a chance to run.
// When we have |g_run_tasks_by_batches| active we want to always set the flag
// to true to have a similar behavior on Android as on the desktop platforms
// for this experiment.
if (RunsTasksByBatches() ||
(!main_thread_only().yield_to_native_after_batch.is_null() &&
continuation_lazy_now.Now() <
main_thread_only().yield_to_native_after_batch)) {
next_work_info.yield_to_native = true;
}
do_work_needed_before_wait_ = false;
// Schedule a continuation.
WorkDeduplicator::NextTask next_task =
(next_wake_up && next_wake_up->is_immediate())
? WorkDeduplicator::NextTask::kIsImmediate
: WorkDeduplicator::NextTask::kIsDelayed;
if (work_deduplicator_.DidCheckForMoreWork(next_task) ==
ShouldScheduleWork::kScheduleImmediate) {
// Need to run new work immediately, but due to the contract of DoWork
// we only need to return a null TimeTicks to ensure that happens.
return next_work_info;
}
// Special-casing here avoids unnecessarily sampling Now() when out of work.
if (!next_wake_up) {
next_work_info.delayed_run_time = TimeTicks::Max();
return next_work_info;
}
// The MessagePump will schedule the wake up on our behalf, so we need to
// update |next_work_info.delayed_run_time|.
TimeTicks next_delayed_do_work = pump_->AdjustDelayedRunTime(
next_wake_up->earliest_time(), next_wake_up->time,
next_wake_up->latest_time());
// Don't request a run time past |main_thread_only().quit_runloop_after|.
if (next_delayed_do_work > main_thread_only().quit_runloop_after) {
next_delayed_do_work = main_thread_only().quit_runloop_after;
// If we've passed |quit_runloop_after| there's no more work to do.
if (continuation_lazy_now.Now() >= main_thread_only().quit_runloop_after) {
next_work_info.delayed_run_time = TimeTicks::Max();
return next_work_info;
}
}
next_work_info.delayed_run_time =
CapAtOneDay(next_delayed_do_work, &continuation_lazy_now);
next_work_info.leeway = GetLeewayForWakeUp(next_wake_up);
next_work_info.recent_now = continuation_lazy_now.Now();
return next_work_info;
}
std::optional<WakeUp> ThreadControllerWithMessagePumpImpl::DoWorkImpl(
LazyNow* continuation_lazy_now) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
"ThreadControllerImpl::DoWork");
if (!main_thread_only().task_execution_allowed) {
// Broadcast in a trace event that application tasks were disallowed. This
// helps spot nested loops that intentionally starve application tasks.
TRACE_EVENT0("base", "ThreadController: application tasks disallowed");
if (main_thread_only().quit_runloop_after == TimeTicks::Max())
return std::nullopt;
return WakeUp{main_thread_only().quit_runloop_after};
}
DCHECK(main_thread_only().task_source);
// Keep running tasks for up to 8ms before yielding to the pump when tasks are
// run by batches.
const base::TimeDelta batch_duration =
RunsTasksByBatches() ? base::Milliseconds(8) : base::Milliseconds(0);
const std::optional<base::TimeTicks> start_time =
batch_duration.is_zero()
? std::nullopt
: std::optional<base::TimeTicks>(time_source_->NowTicks());
std::optional<base::TimeTicks> recent_time = start_time;
// Loops for |batch_duration|, or |work_batch_size| times if |batch_duration|
// is zero.
for (int num_tasks_executed = 0;
(!batch_duration.is_zero() &&
(recent_time.value() - start_time.value()) < batch_duration) ||
(batch_duration.is_zero() &&
num_tasks_executed < main_thread_only().work_batch_size);
++num_tasks_executed) {
LazyNow lazy_now_select_task(recent_time, time_source_);
// Include SelectNextTask() in the scope of the work item. This ensures
// it's covered in tracing and hang reports. This is particularly
// important when SelectNextTask() finds no work immediately after a
// wakeup, otherwise the power-inefficient wakeup is invisible in
// tracing. OnApplicationTaskSelected() assumes this ordering as well.
OnBeginWorkItemImpl(lazy_now_select_task);
int run_depth = static_cast<int>(run_level_tracker_.num_run_levels());
const SequencedTaskSource::SelectTaskOption select_task_option =
power_monitor_.IsProcessInPowerSuspendState()
? SequencedTaskSource::SelectTaskOption::kSkipDelayedTask
: SequencedTaskSource::SelectTaskOption::kDefault;
std::optional<SequencedTaskSource::SelectedTask> selected_task =
main_thread_only().task_source->SelectNextTask(lazy_now_select_task,
select_task_option);
LazyNow lazy_now_task_selected(time_source_);
run_level_tracker_.OnApplicationTaskSelected(
(selected_task && selected_task->task.delayed_run_time.is_null())
? selected_task->task.queue_time
: TimeTicks(),
lazy_now_task_selected);
if (!selected_task) {
OnEndWorkItemImpl(lazy_now_task_selected, run_depth);
break;
}
// Execute the task and assume the worst: it is probably not reentrant.
AutoReset<bool> ban_nested_application_tasks(
&main_thread_only().task_execution_allowed, false);
// Trace-parsing tools (DevTools, Lighthouse, etc) consume this event to
// determine long tasks.
// See https://crbug.com/681863 and https://crbug.com/874982
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "RunTask");
{
// Always track the start of the task, as this is low-overhead.
TaskAnnotator::LongTaskTracker long_task_tracker(
time_source_, selected_task->task, &task_annotator_,
lazy_now_task_selected.Now());
// Note: all arguments after task are just passed to a TRACE_EVENT for
// logging so lambda captures are safe as lambda is executed inline.
SequencedTaskSource* source = main_thread_only().task_source;
task_annotator_.RunTask(
"ThreadControllerImpl::RunTask", selected_task->task,
[&selected_task, &source](perfetto::EventContext& ctx) {
if (selected_task->task_execution_trace_logger) {
selected_task->task_execution_trace_logger.Run(
ctx, selected_task->task);
}
source->MaybeEmitTaskDetails(ctx, selected_task.value());
});
}
// Reset `selected_task` before the call to `DidRunTask()` below makes its
// `PendingTask` reference dangling.
selected_task.reset();
LazyNow lazy_now_after_run_task(time_source_);
main_thread_only().task_source->DidRunTask(lazy_now_after_run_task);
// End the work item scope after DidRunTask() as it can process microtasks
// (which are extensions of the RunTask).
OnEndWorkItemImpl(lazy_now_after_run_task, run_depth);
// If DidRunTask() read the clock (lazy_now_after_run_task.has_value()) or
// if |batch_duration| > 0, store the clock value in `recent_time` so it can
// be reused by SelectNextTask() at the next loop iteration.
if (lazy_now_after_run_task.has_value() || !batch_duration.is_zero()) {
recent_time = lazy_now_after_run_task.Now();
} else {
recent_time.reset();
}
// When Quit() is called we must stop running the batch because the
// caller expects per-task granularity.
if (main_thread_only().quit_pending)
break;
}
if (main_thread_only().quit_pending)
return std::nullopt;
work_deduplicator_.WillCheckForMoreWork();
// Re-check the state of the power after running tasks. An executed task may
// have been a power change notification.
const SequencedTaskSource::SelectTaskOption select_task_option =
power_monitor_.IsProcessInPowerSuspendState()
? SequencedTaskSource::SelectTaskOption::kSkipDelayedTask
: SequencedTaskSource::SelectTaskOption::kDefault;
return main_thread_only().task_source->GetPendingWakeUp(continuation_lazy_now,
select_task_option);
}
bool ThreadControllerWithMessagePumpImpl::RunsTasksByBatches() const {
return can_run_tasks_by_batches_ &&
g_run_tasks_by_batches.load(std::memory_order_relaxed);
}
void ThreadControllerWithMessagePumpImpl::DoIdleWork() {
struct OnIdle {
STACK_ALLOCATED();
public:
OnIdle(const TickClock* time_source, RunLevelTracker& run_level_tracker_ref)
: lazy_now(time_source), run_level_tracker(run_level_tracker_ref) {}
// Very last step before going idle, must be fast as this is hidden from the
// DoIdleWork trace event below.
~OnIdle() { run_level_tracker.OnIdle(lazy_now); }
LazyNow lazy_now;
private:
RunLevelTracker& run_level_tracker;
};
std::optional<OnIdle> on_idle;
// Must be after `on_idle` as this trace event's scope must end before the END
// of the "ThreadController active" trace event emitted from
// `run_level_tracker_.OnIdle()`.
TRACE_EVENT0("sequence_manager", "SequenceManager::DoIdleWork");
#if BUILDFLAG(IS_WIN)
if (!power_monitor_.IsProcessInPowerSuspendState()) {
// Avoid calling Time::ActivateHighResolutionTimer() between
// suspend/resume as the system hangs if we do (crbug.com/1074028).
// OnResume() will generate a task on this thread per the
// ThreadControllerPowerMonitor observer and DoIdleWork() will thus get
// another chance to set the right high-resolution-timer-state before
// going to sleep after resume.
const bool need_high_res_mode =
main_thread_only().task_source->HasPendingHighResolutionTasks();
if (main_thread_only().in_high_res_mode != need_high_res_mode) {
// On Windows we activate the high resolution timer so that the wait
// _if_ triggered by the timer happens with good resolution. If we don't
// do this the default resolution is 15ms which might not be acceptable
// for some tasks.
main_thread_only().in_high_res_mode = need_high_res_mode;
Time::ActivateHighResolutionTimer(need_high_res_mode);
}
}
#endif // BUILDFLAG(IS_WIN)
if (main_thread_only().task_source->OnIdle()) {
work_id_provider_->IncrementWorkId();
// The OnIdle() callback resulted in more immediate work, so schedule a
// DoWork callback. For some message pumps returning true from here is
// sufficient to do that but not on mac.
pump_->ScheduleWork();
return;
}
work_id_provider_->IncrementWorkId();
// This is mostly redundant with the identical call in BeforeWait (upcoming)
// but some uninstrumented MessagePump impls don't call BeforeWait so it must
// also be done here.
hang_watch_scope_.reset();
// All return paths below are truly idle.
on_idle.emplace(time_source_, run_level_tracker_);
// Check if any runloop timeout has expired.
if (main_thread_only().quit_runloop_after != TimeTicks::Max() &&
main_thread_only().quit_runloop_after <= on_idle->lazy_now.Now()) {
Quit();
return;
}
// RunLoop::Delegate knows whether we called Run() or RunUntilIdle().
if (ShouldQuitWhenIdle())
Quit();
}
int ThreadControllerWithMessagePumpImpl::RunDepth() {
return static_cast<int>(run_level_tracker_.num_run_levels());
}
void ThreadControllerWithMessagePumpImpl::Run(bool application_tasks_allowed,
TimeDelta timeout) {
DCHECK(RunsTasksInCurrentSequence());
// Inside a `RunLoop`, all work that has mutual exclusion or ordering
// expectations with the task source is tracked, so it's safe to allow running
// tasks synchronously in `RunOrPostTask()`.
main_thread_only().task_source->SetRunTaskSynchronouslyAllowed(true);
LazyNow lazy_now_run_loop_start(time_source_);
// RunLoops can be nested so we need to restore the previous value of
// |quit_runloop_after| upon exit. NB we could use saturated arithmetic here
// but don't because we have some tests which assert the number of calls to
// Now.
AutoReset<TimeTicks> quit_runloop_after(
&main_thread_only().quit_runloop_after,
(timeout == TimeDelta::Max()) ? TimeTicks::Max()
: lazy_now_run_loop_start.Now() + timeout);
run_level_tracker_.OnRunLoopStarted(RunLevelTracker::kInBetweenWorkItems,
lazy_now_run_loop_start);
// Quit may have been called outside of a Run(), so |quit_pending| might be
// true here. We can't use InTopLevelDoWork() in Quit() as this call may be
// outside top-level DoWork but still in Run().
main_thread_only().quit_pending = false;
hang_watch_scope_.emplace();
if (application_tasks_allowed && !main_thread_only().task_execution_allowed) {
// Allow nested task execution as explicitly requested.
DCHECK(RunLoop::IsNestedOnCurrentThread());
main_thread_only().task_execution_allowed = true;
pump_->Run(this);
main_thread_only().task_execution_allowed = false;
} else {
pump_->Run(this);
}
run_level_tracker_.OnRunLoopEnded();
main_thread_only().quit_pending = false;
// If this was a nested loop, hang watch the remainder of the task which
// caused it. Otherwise, stop watching as we're no longer running.
if (RunLoop::IsNestedOnCurrentThread()) {
hang_watch_scope_.emplace();
} else {
hang_watch_scope_.reset();
}
work_id_provider_->IncrementWorkId();
// Work outside of a `RunLoop` may have mutual exclusion or ordering
// guarantees with the task source, so disallow running tasks synchronously in
// `RunOrPostTask()`.
if (run_level_tracker_.num_run_levels() == 0) {
main_thread_only().task_source->SetRunTaskSynchronouslyAllowed(false);
}
}
void ThreadControllerWithMessagePumpImpl::OnBeginNestedRunLoop() {
// We don't need to ScheduleWork here! That's because the call to pump_->Run()
// above, which is always called for RunLoop().Run(), guarantees a call to
// DoWork on all platforms.
if (main_thread_only().nesting_observer)
main_thread_only().nesting_observer->OnBeginNestedRunLoop();
}
void ThreadControllerWithMessagePumpImpl::OnExitNestedRunLoop() {
if (main_thread_only().nesting_observer)
main_thread_only().nesting_observer->OnExitNestedRunLoop();
}
void ThreadControllerWithMessagePumpImpl::Quit() {
DCHECK(RunsTasksInCurrentSequence());
// Interrupt a batch of work.
main_thread_only().quit_pending = true;
// If we're in a nested RunLoop, continuation will be posted if necessary.
pump_->Quit();
}
void ThreadControllerWithMessagePumpImpl::EnsureWorkScheduled() {
if (work_deduplicator_.OnWorkRequested() ==
ShouldScheduleWork::kScheduleImmediate) {
pump_->ScheduleWork();
}
}
void ThreadControllerWithMessagePumpImpl::
SetTaskExecutionAllowedInNativeNestedLoop(bool allowed) {
if (allowed) {
// We need to schedule work unconditionally because we might be about to
// enter an OS level nested message loop. Unlike a RunLoop().Run() we don't
// get a call to DoWork on entering for free.
work_deduplicator_.OnWorkRequested(); // Set the pending DoWork flag.
} else {
// We've (probably) just left an OS level nested message loop. Make sure a
// subsequent PostTask within the same Task doesn't ScheduleWork with the
// pump (this will be done anyway when the task exits).
work_deduplicator_.OnWorkStarted();
}
if (!pump_->HandleNestedNativeLoopWithApplicationTasks(allowed)) {
// Pump does not have its own support for native nested loops,
// ThreadController must handle scheduling for upcoming tasks.
if (allowed) {
pump_->ScheduleWork();
}
}
task_execution_allowed_in_native_nested_loop_ = allowed;
main_thread_only().task_execution_allowed = allowed;
}
bool ThreadControllerWithMessagePumpImpl::IsTaskExecutionAllowed() const {
return main_thread_only().task_execution_allowed;
}
MessagePump* ThreadControllerWithMessagePumpImpl::GetBoundMessagePump() const {
return pump_.get();
}
void ThreadControllerWithMessagePumpImpl::PrioritizeYieldingToNative(
base::TimeTicks prioritize_until) {
main_thread_only().yield_to_native_after_batch = prioritize_until;
}
#if BUILDFLAG(IS_IOS)
void ThreadControllerWithMessagePumpImpl::AttachToMessagePump() {
static_cast<MessagePumpCFRunLoopBase*>(pump_.get())->Attach(this);
}
void ThreadControllerWithMessagePumpImpl::DetachFromMessagePump() {
static_cast<MessagePumpCFRunLoopBase*>(pump_.get())->Detach();
}
#elif BUILDFLAG(IS_ANDROID)
void ThreadControllerWithMessagePumpImpl::AttachToMessagePump() {
CHECK(main_thread_only().work_batch_size == 1);
// Aborting the message pump currently relies on the batch size being 1.
main_thread_only().can_change_batch_size = false;
static_cast<MessagePumpForUI*>(pump_.get())->Attach(this);
}
#endif
bool ThreadControllerWithMessagePumpImpl::ShouldQuitRunLoopWhenIdle() {
if (run_level_tracker_.num_run_levels() == 0)
return false;
// It's only safe to call ShouldQuitWhenIdle() when in a RunLoop.
return ShouldQuitWhenIdle();
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,224 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_WITH_MESSAGE_PUMP_IMPL_H_
#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_WITH_MESSAGE_PUMP_IMPL_H_
#include <memory>
#include <optional>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/message_loop/message_pump.h"
#include "base/message_loop/work_id_provider.h"
#include "base/run_loop.h"
#include "base/task/common/checked_lock.h"
#include "base/task/common/task_annotator.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/sequenced_task_source.h"
#include "base/task/sequence_manager/thread_controller.h"
#include "base/task/sequence_manager/thread_controller_power_monitor.h"
#include "base/task/sequence_manager/work_deduplicator.h"
#include "base/thread_annotations.h"
#include "base/threading/hang_watcher.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequence_local_storage_map.h"
#include "build/build_config.h"
namespace base {
namespace sequence_manager {
namespace internal {
// This is the interface between the SequenceManager and the MessagePump.
class BASE_EXPORT ThreadControllerWithMessagePumpImpl
: public ThreadController,
public MessagePump::Delegate,
public RunLoop::Delegate,
public RunLoop::NestingObserver {
public:
static void InitializeFeatures();
static void ResetFeatures();
ThreadControllerWithMessagePumpImpl(
std::unique_ptr<MessagePump> message_pump,
const SequenceManager::Settings& settings);
ThreadControllerWithMessagePumpImpl(
const ThreadControllerWithMessagePumpImpl&) = delete;
ThreadControllerWithMessagePumpImpl& operator=(
const ThreadControllerWithMessagePumpImpl&) = delete;
~ThreadControllerWithMessagePumpImpl() override;
using ShouldScheduleWork = WorkDeduplicator::ShouldScheduleWork;
static std::unique_ptr<ThreadControllerWithMessagePumpImpl> CreateUnbound(
const SequenceManager::Settings& settings);
// ThreadController implementation:
void SetSequencedTaskSource(SequencedTaskSource* task_source) override;
void BindToCurrentThread(std::unique_ptr<MessagePump> message_pump) override;
void SetWorkBatchSize(int work_batch_size) override;
void WillQueueTask(PendingTask* pending_task) override;
void ScheduleWork() override;
void SetNextDelayedDoWork(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) override;
bool RunsTasksInCurrentSequence() override;
void SetDefaultTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) override;
scoped_refptr<SingleThreadTaskRunner> GetDefaultTaskRunner() override;
void RestoreDefaultTaskRunner() override;
void AddNestingObserver(RunLoop::NestingObserver* observer) override;
void RemoveNestingObserver(RunLoop::NestingObserver* observer) override;
void SetTaskExecutionAllowedInNativeNestedLoop(bool allowed) override;
bool IsTaskExecutionAllowed() const override;
MessagePump* GetBoundMessagePump() const override;
void PrioritizeYieldingToNative(base::TimeTicks prioritize_until) override;
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
void AttachToMessagePump() override;
#endif
#if BUILDFLAG(IS_IOS)
void DetachFromMessagePump() override;
#endif
bool ShouldQuitRunLoopWhenIdle() override;
// RunLoop::NestingObserver:
void OnBeginNestedRunLoop() override;
void OnExitNestedRunLoop() override;
protected:
explicit ThreadControllerWithMessagePumpImpl(
const SequenceManager::Settings& settings);
// MessagePump::Delegate implementation.
void OnBeginWorkItem() override;
void OnEndWorkItem(int run_level_depth) override;
void BeforeWait() override;
void BeginNativeWorkBeforeDoWork() override;
MessagePump::Delegate::NextWorkInfo DoWork() override;
void DoIdleWork() override;
int RunDepth() override;
void OnBeginWorkItemImpl(LazyNow& lazy_now);
void OnEndWorkItemImpl(LazyNow& lazy_now, int run_level_depth);
// RunLoop::Delegate implementation.
void Run(bool application_tasks_allowed, TimeDelta timeout) override;
void Quit() override;
void EnsureWorkScheduled() override;
struct MainThreadOnly {
MainThreadOnly();
~MainThreadOnly();
raw_ptr<SequencedTaskSource> task_source = nullptr; // Not owned.
raw_ptr<RunLoop::NestingObserver> nesting_observer = nullptr; // Not owned.
std::unique_ptr<SingleThreadTaskRunner::CurrentDefaultHandle>
thread_task_runner_handle;
// Indicates that we should yield DoWork between each task to let a possibly
// nested RunLoop exit.
bool quit_pending = false;
// Whether high resolution timing is enabled or not.
bool in_high_res_mode = false;
// Number of tasks processed in a single DoWork invocation.
int work_batch_size = 1;
bool can_change_batch_size = true;
// While Now() is less than |yield_to_native_after_batch| we will request a
// yield to the MessagePump after |work_batch_size| work items.
base::TimeTicks yield_to_native_after_batch = base::TimeTicks();
// The time after which the runloop should quit.
TimeTicks quit_runloop_after = TimeTicks::Max();
bool task_execution_allowed = true;
};
const MainThreadOnly& MainThreadOnlyForTesting() const {
return main_thread_only_;
}
ThreadControllerPowerMonitor* ThreadControllerPowerMonitorForTesting() {
return &power_monitor_;
}
private:
friend class DoWorkScope;
friend class RunScope;
// Returns a WakeUp for the next pending task, is_immediate() if the next task
// can run immediately, or nullopt if there are no more immediate or delayed
// tasks.
std::optional<WakeUp> DoWorkImpl(LazyNow* continuation_lazy_now);
bool RunsTasksByBatches() const;
void InitializeSingleThreadTaskRunnerCurrentDefaultHandle()
EXCLUSIVE_LOCKS_REQUIRED(task_runner_lock_);
MainThreadOnly& main_thread_only() LIFETIME_BOUND {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
return main_thread_only_;
}
const MainThreadOnly& main_thread_only() const LIFETIME_BOUND {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
return main_thread_only_;
}
MainThreadOnly main_thread_only_;
mutable base::internal::CheckedLock task_runner_lock_;
scoped_refptr<SingleThreadTaskRunner> task_runner_
GUARDED_BY(task_runner_lock_);
WorkDeduplicator work_deduplicator_;
bool do_work_needed_before_wait_ = false;
bool task_execution_allowed_in_native_nested_loop_ = false;
ThreadControllerPowerMonitor power_monitor_;
TaskAnnotator task_annotator_;
// Non-null provider of id state for identifying distinct work items executed
// by the message loop (task, event, etc.). Cached on the class to avoid TLS
// lookups on task execution.
raw_ptr<WorkIdProvider> work_id_provider_ = nullptr;
// Required to register the current thread as a sequence. Must be declared
// after |main_thread_only_| so that the destructors of state stored in the
// map run while the main thread state is still valid (crbug.com/1221382)
base::internal::SequenceLocalStorageMap sequence_local_storage_map_;
std::unique_ptr<
base::internal::ScopedSetSequenceLocalStorageMapForCurrentThread>
scoped_set_sequence_local_storage_map_for_current_thread_;
// Whether tasks can run by batches (i.e. multiple tasks run between each
// check for native work). Tasks will only run by batches if this is true and
// the "RunTasksByBatches" feature is enabled.
bool can_run_tasks_by_batches_ = false;
// Reset at the start & end of each unit of work to cover the work itself and
// the overhead between each work item (no-op if HangWatcher is not enabled
// on this thread). Cleared when going to sleep and at the end of a Run()
// (i.e. when Quit()). Nested runs override their parent.
std::optional<WatchHangsInScope> hang_watch_scope_;
// Can only be set once (just before calling
// work_deduplicator_.BindToCurrentThread()). After that only read access is
// allowed.
// NOTE: |pump_| accesses other members but other members should not access
// |pump_|. This means that it should be destroyed first. This member cannot
// be moved up.
std::unique_ptr<MessagePump> pump_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_WITH_MESSAGE_PUMP_IMPL_H_

View File

@@ -0,0 +1,32 @@
// 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/time_domain.h"
#include <optional>
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/threading/thread_checker.h"
namespace base {
namespace sequence_manager {
void TimeDomain::NotifyPolicyChanged() {
sequence_manager_->ScheduleWork();
}
void TimeDomain::OnAssignedToSequenceManager(
internal::SequenceManagerImpl* sequence_manager) {
DCHECK(sequence_manager);
sequence_manager_ = sequence_manager;
}
Value::Dict TimeDomain::AsValue() const {
Value::Dict state;
state.Set("name", GetName());
return state;
}
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,70 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_TIME_DOMAIN_H_
#define BASE_TASK_SEQUENCE_MANAGER_TIME_DOMAIN_H_
#include <optional>
#include "base/base_export.h"
#include "base/check.h"
#include "base/memory/raw_ptr.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/tasks.h"
#include "base/time/tick_clock.h"
#include "base/values.h"
namespace base {
namespace sequence_manager {
class SequenceManager;
namespace internal {
class SequenceManagerImpl;
} // namespace internal
// TimeDomain allows subclasses to enable clock overriding
// (e.g. auto-advancing virtual time, throttled clock, etc).
class BASE_EXPORT TimeDomain : public TickClock {
public:
TimeDomain(const TimeDomain&) = delete;
TimeDomain& operator=(const TimeDomain&) = delete;
~TimeDomain() override = default;
// Invoked when the thread reaches idle. Gives an opportunity to a virtual
// time domain impl to fast-forward time and return true to indicate that
// there's more work to run. If RunLoop::QuitWhenIdle has been called then
// `quit_when_idle_requested` will be true.
virtual bool MaybeFastForwardToWakeUp(std::optional<WakeUp> next_wake_up,
bool quit_when_idle_requested) = 0;
// Debug info.
Value::Dict AsValue() const;
protected:
TimeDomain() = default;
virtual const char* GetName() const = 0;
// Tells SequenceManager that internal policy might have changed to
// re-evaluate MaybeFastForwardToWakeUp().
void NotifyPolicyChanged();
// Called when the TimeDomain is assigned to a SequenceManagerImpl.
// `sequence_manager` is expected to be valid for the duration of TimeDomain's
// existence. TODO(scheduler-dev): Pass SequenceManager in the constructor.
void OnAssignedToSequenceManager(
internal::SequenceManagerImpl* sequence_manager);
private:
friend class internal::SequenceManagerImpl;
raw_ptr<internal::SequenceManagerImpl, DanglingUntriaged> sequence_manager_ =
nullptr;
};
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_TIME_DOMAIN_H_

View File

@@ -0,0 +1,191 @@
// Copyright 2021 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/wake_up_queue.h"
#include <optional>
#include "base/task/sequence_manager/associated_thread_id.h"
#include "base/task/sequence_manager/sequence_manager_impl.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/threading/thread_checker.h"
namespace base {
namespace sequence_manager {
namespace internal {
WakeUpQueue::WakeUpQueue(
scoped_refptr<const internal::AssociatedThreadId> associated_thread)
: associated_thread_(std::move(associated_thread)) {}
WakeUpQueue::~WakeUpQueue() {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
}
void WakeUpQueue::RemoveAllCanceledDelayedTasksFromFront(LazyNow* lazy_now) {
// Repeatedly trim the front of the top queue until it stabilizes. This is
// needed because a different queue can become the top one once you remove the
// canceled tasks.
while (!wake_up_queue_.empty()) {
auto* top_queue = wake_up_queue_.top().queue;
// If no tasks are removed from the top queue, then it means the top queue
// cannot change anymore.
if (!top_queue->RemoveAllCanceledDelayedTasksFromFront(lazy_now))
break;
}
}
// TODO(kraynov): https://crbug.com/857101 Consider making an interface
// for SequenceManagerImpl which will expose SetNextDelayedDoWork and
// MaybeScheduleImmediateWork methods to make the functions below pure-virtual.
void WakeUpQueue::SetNextWakeUpForQueue(internal::TaskQueueImpl* queue,
LazyNow* lazy_now,
std::optional<WakeUp> wake_up) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK_EQ(queue->wake_up_queue(), this);
DCHECK(queue->IsQueueEnabled() || !wake_up);
std::optional<WakeUp> previous_wake_up = GetNextDelayedWakeUp();
std::optional<WakeUpResolution> previous_queue_resolution;
if (queue->heap_handle().IsValid()) {
previous_queue_resolution =
wake_up_queue_.at(queue->heap_handle()).wake_up.resolution;
}
if (wake_up) {
// Insert a new wake-up into the heap.
if (queue->heap_handle().IsValid()) {
// O(log n)
wake_up_queue_.Replace(queue->heap_handle(), {wake_up.value(), queue});
} else {
// O(log n)
wake_up_queue_.insert({wake_up.value(), queue});
}
} else {
// Remove a wake-up from heap if present.
if (queue->heap_handle().IsValid())
wake_up_queue_.erase(queue->heap_handle());
}
std::optional<WakeUp> new_wake_up = GetNextDelayedWakeUp();
if (previous_queue_resolution &&
*previous_queue_resolution == WakeUpResolution::kHigh) {
pending_high_res_wake_up_count_--;
}
if (wake_up && wake_up->resolution == WakeUpResolution::kHigh)
pending_high_res_wake_up_count_++;
DCHECK_GE(pending_high_res_wake_up_count_, 0);
if (new_wake_up != previous_wake_up)
OnNextWakeUpChanged(lazy_now, GetNextDelayedWakeUp());
}
void WakeUpQueue::MoveReadyDelayedTasksToWorkQueues(
LazyNow* lazy_now,
EnqueueOrder enqueue_order) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
bool update_needed = false;
while (!wake_up_queue_.empty() &&
wake_up_queue_.top().wake_up.earliest_time() <= lazy_now->Now()) {
internal::TaskQueueImpl* queue = wake_up_queue_.top().queue;
// OnWakeUp() is expected to update the next wake-up for this queue with
// SetNextWakeUpForQueue(), thus allowing us to make progress.
queue->OnWakeUp(lazy_now, enqueue_order);
update_needed = true;
}
if (!update_needed || wake_up_queue_.empty())
return;
// If any queue was notified, possibly update following queues. This ensures
// the wake up is up to date, which is necessary because calling OnWakeUp() on
// a throttled queue may affect state that is shared between other related
// throttled queues. The wake up for an affected queue might be pushed back
// and needs to be updated. This is done lazily only once the related queue
// becomes the next one to wake up, since that wake up can't be moved up.
// `wake_up_queue_` is non-empty here, per the condition above.
internal::TaskQueueImpl* queue = wake_up_queue_.top().queue;
queue->UpdateWakeUp(lazy_now);
while (!wake_up_queue_.empty()) {
internal::TaskQueueImpl* old_queue =
std::exchange(queue, wake_up_queue_.top().queue);
if (old_queue == queue)
break;
queue->UpdateWakeUp(lazy_now);
}
}
std::optional<WakeUp> WakeUpQueue::GetNextDelayedWakeUp() const {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
if (wake_up_queue_.empty())
return std::nullopt;
WakeUp wake_up = wake_up_queue_.top().wake_up;
// `wake_up.resolution` is not meaningful since it may be different from
// has_pending_high_resolution_tasks(). Return WakeUpResolution::kLow here to
// simplify comparison between wake ups.
// TODO(crbug.com/40158967): Drive resolution by DelayPolicy and return
// has_pending_high_resolution_tasks() here.
wake_up.resolution = WakeUpResolution::kLow;
return wake_up;
}
Value::Dict WakeUpQueue::AsValue(TimeTicks now) const {
Value::Dict state;
state.Set("name", GetName());
// TODO(crbug.com/40228085): Make base::Value able to store an int64_t and
// remove this cast.
state.Set("registered_delay_count", checked_cast<int>(wake_up_queue_.size()));
if (!wake_up_queue_.empty()) {
TimeDelta delay = wake_up_queue_.top().wake_up.time - now;
state.Set("next_delay_ms", delay.InMillisecondsF());
}
return state;
}
DefaultWakeUpQueue::DefaultWakeUpQueue(
scoped_refptr<internal::AssociatedThreadId> associated_thread,
internal::SequenceManagerImpl* sequence_manager)
: WakeUpQueue(std::move(associated_thread)),
sequence_manager_(sequence_manager) {}
DefaultWakeUpQueue::~DefaultWakeUpQueue() = default;
void DefaultWakeUpQueue::OnNextWakeUpChanged(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) {
sequence_manager_->SetNextWakeUp(lazy_now, wake_up);
}
void DefaultWakeUpQueue::UnregisterQueue(internal::TaskQueueImpl* queue) {
DCHECK_EQ(queue->wake_up_queue(), this);
LazyNow lazy_now(sequence_manager_->main_thread_clock());
SetNextWakeUpForQueue(queue, &lazy_now, std::nullopt);
}
const char* DefaultWakeUpQueue::GetName() const {
return "DefaultWakeUpQueue";
}
NonWakingWakeUpQueue::NonWakingWakeUpQueue(
scoped_refptr<internal::AssociatedThreadId> associated_thread)
: WakeUpQueue(std::move(associated_thread)) {}
NonWakingWakeUpQueue::~NonWakingWakeUpQueue() = default;
void NonWakingWakeUpQueue::OnNextWakeUpChanged(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) {}
const char* NonWakingWakeUpQueue::GetName() const {
return "NonWakingWakeUpQueue";
}
void NonWakingWakeUpQueue::UnregisterQueue(internal::TaskQueueImpl* queue) {
DCHECK_EQ(queue->wake_up_queue(), this);
SetNextWakeUpForQueue(queue, nullptr, std::nullopt);
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,158 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_WAKE_UP_QUEUE_H_
#define BASE_TASK_SEQUENCE_MANAGER_WAKE_UP_QUEUE_H_
#include <optional>
#include "base/base_export.h"
#include "base/check.h"
#include "base/containers/intrusive_heap.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/task/common/lazy_now.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/time/time.h"
#include "base/values.h"
namespace base {
namespace sequence_manager {
class EnqueueOrder;
namespace internal {
class AssociatedThreadId;
class SequenceManagerImpl;
class TaskQueueImpl;
// WakeUpQueue is a queue of (wake_up, TaskQueueImpl*) pairs which
// aggregates wake-ups from multiple TaskQueueImpl into a single wake-up, and
// notifies TaskQueueImpls when wake-up times are reached.
class BASE_EXPORT WakeUpQueue {
public:
WakeUpQueue(const WakeUpQueue&) = delete;
WakeUpQueue& operator=(const WakeUpQueue&) = delete;
virtual ~WakeUpQueue();
// Returns a wake-up for the next pending delayed task (pending delayed tasks
// that are ripe may be ignored). If there are no such tasks (immediate tasks
// don't count) or queues are disabled it returns nullopt.
std::optional<WakeUp> GetNextDelayedWakeUp() const;
// Debug info.
Value::Dict AsValue(TimeTicks now) const;
bool has_pending_high_resolution_tasks() const {
return pending_high_res_wake_up_count_;
}
// Returns true if there are no pending delayed tasks.
bool empty() const { return wake_up_queue_.empty(); }
// Moves ready delayed tasks in TaskQueues to delayed WorkQueues, consuming
// expired wake-ups in the process.
void MoveReadyDelayedTasksToWorkQueues(LazyNow* lazy_now,
EnqueueOrder enqueue_order);
// Schedule `queue` to wake up at certain time. Repeating calls with the same
// `queue` invalidate previous requests. Nullopt `wake_up` cancels a
// previously set wake up for `queue`.
void SetNextWakeUpForQueue(internal::TaskQueueImpl* queue,
LazyNow* lazy_now,
std::optional<WakeUp> wake_up);
// Remove the TaskQueue from any internal data structures.
virtual void UnregisterQueue(internal::TaskQueueImpl* queue) = 0;
// Removes all canceled delayed tasks from the front of the queue. After
// calling this, GetNextDelayedWakeUp() is guaranteed to return a wake up time
// for a non-canceled task.
void RemoveAllCanceledDelayedTasksFromFront(LazyNow* lazy_now);
protected:
explicit WakeUpQueue(
scoped_refptr<const internal::AssociatedThreadId> associated_thread);
// Called every time the next `next_wake_up` changes. std::nullopt is used to
// cancel the next wake-up. Subclasses may use this to tell SequenceManager to
// schedule the next wake-up at the given time.
virtual void OnNextWakeUpChanged(LazyNow* lazy_now,
std::optional<WakeUp> next_wake_up) = 0;
virtual const char* GetName() const = 0;
private:
friend class MockWakeUpQueue;
struct ScheduledWakeUp {
WakeUp wake_up;
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of
// speedometer3).
RAW_PTR_EXCLUSION internal::TaskQueueImpl* queue = nullptr;
bool operator>(const ScheduledWakeUp& other) const {
return wake_up.latest_time() > other.wake_up.latest_time();
}
void SetHeapHandle(HeapHandle handle) {
DCHECK(handle.IsValid());
queue->set_heap_handle(handle);
}
void ClearHeapHandle() {
DCHECK(queue->heap_handle().IsValid());
queue->set_heap_handle(HeapHandle());
}
HeapHandle GetHeapHandle() const { return queue->heap_handle(); }
};
IntrusiveHeap<ScheduledWakeUp, std::greater<>> wake_up_queue_;
int pending_high_res_wake_up_count_ = 0;
const scoped_refptr<const internal::AssociatedThreadId> associated_thread_;
};
// Default WakeUpQueue implementation that forwards wake-ups to
// `sequence_manager_`.
class BASE_EXPORT DefaultWakeUpQueue : public WakeUpQueue {
public:
DefaultWakeUpQueue(
scoped_refptr<internal::AssociatedThreadId> associated_thread,
internal::SequenceManagerImpl* sequence_manager);
~DefaultWakeUpQueue() override;
private:
// WakeUpQueue implementation:
void OnNextWakeUpChanged(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) override;
const char* GetName() const override;
void UnregisterQueue(internal::TaskQueueImpl* queue) override;
raw_ptr<internal::SequenceManagerImpl> sequence_manager_; // Not owned.
};
// WakeUpQueue implementation that doesn't sends wake-ups to
// any SequenceManager, such that task queues don't cause wake-ups.
class BASE_EXPORT NonWakingWakeUpQueue : public WakeUpQueue {
public:
explicit NonWakingWakeUpQueue(
scoped_refptr<internal::AssociatedThreadId> associated_thread);
~NonWakingWakeUpQueue() override;
private:
// WakeUpQueue implementation:
void OnNextWakeUpChanged(LazyNow* lazy_now,
std::optional<WakeUp> wake_up) override;
const char* GetName() const override;
void UnregisterQueue(internal::TaskQueueImpl* queue) override;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_WAKE_UP_QUEUE_H_

View File

@@ -0,0 +1,78 @@
// Copyright 2019 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_deduplicator.h"
#include <ostream>
#include <utility>
#include "base/check_op.h"
namespace base {
namespace sequence_manager {
namespace internal {
WorkDeduplicator::WorkDeduplicator(
scoped_refptr<const AssociatedThreadId> associated_thread)
: associated_thread_(std::move(associated_thread)) {}
WorkDeduplicator::~WorkDeduplicator() = default;
WorkDeduplicator::ShouldScheduleWork WorkDeduplicator::BindToCurrentThread() {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
int previous_flags = state_.fetch_or(kBoundFlag);
DCHECK_EQ(previous_flags & kBoundFlag, 0) << "Can't bind twice!";
return previous_flags & kPendingDoWorkFlag
? ShouldScheduleWork::kScheduleImmediate
: ShouldScheduleWork::kNotNeeded;
}
WorkDeduplicator::ShouldScheduleWork WorkDeduplicator::OnWorkRequested() {
// Set kPendingDoWorkFlag and return true if we were previously kIdle.
return state_.fetch_or(kPendingDoWorkFlag) == State::kIdle
? ShouldScheduleWork::kScheduleImmediate
: ShouldScheduleWork::kNotNeeded;
}
WorkDeduplicator::ShouldScheduleWork WorkDeduplicator::OnDelayedWorkRequested()
const {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
// This must be called on the associated thread or this read is racy.
return state_.load() == State::kIdle ? ShouldScheduleWork::kScheduleImmediate
: ShouldScheduleWork::kNotNeeded;
}
void WorkDeduplicator::OnWorkStarted() {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK_EQ(state_.load() & kBoundFlag, kBoundFlag);
// Clear kPendingDoWorkFlag and mark us as in a DoWork.
state_.store(State::kInDoWork);
}
void WorkDeduplicator::WillCheckForMoreWork() {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK_EQ(state_.load() & kBoundFlag, kBoundFlag);
// Clear kPendingDoWorkFlag if it was set.
state_.store(State::kInDoWork);
}
WorkDeduplicator::ShouldScheduleWork WorkDeduplicator::DidCheckForMoreWork(
NextTask next_task) {
DCHECK_CALLED_ON_VALID_THREAD(associated_thread_->thread_checker);
DCHECK_EQ(state_.load() & kBoundFlag, kBoundFlag);
if (next_task == NextTask::kIsImmediate) {
state_.store(State::kDoWorkPending);
return ShouldScheduleWork::kScheduleImmediate;
}
// If |next_task| is not immediate, there's still a possibility that
// OnWorkRequested() was invoked racily from another thread just after this
// thread determined that the next task wasn't immediate. In that case, that
// other thread relies on us to return kScheduleImmediate.
return (state_.fetch_and(~kInDoWorkFlag) & kPendingDoWorkFlag)
? ShouldScheduleWork::kScheduleImmediate
: ShouldScheduleWork::kNotNeeded;
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,145 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
#define BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_
#include <atomic>
#include "base/base_export.h"
#include "base/task/sequence_manager/associated_thread_id.h"
namespace base {
namespace sequence_manager {
namespace internal {
// This class's job is to prevent redundant DoWorks being posted, which are
// expensive. The idea is a DoWork will (maybe) run a task before computing the
// delay till the next task. If the task run posts another task, we don't want
// it to schedule work because the DoWork will post a continuation as needed
// with the latest state taken into consideration (fences, enable / disable
// queue, task cancellation, etc...) Other threads can also post DoWork at any
// time, including while we're computing the delay till the next task. To
// account for that, we have split a DoWork up into two sections:
// [OnWorkStarted .. WillCheckForMoreWork] and
// [WillCheckForMoreWork .. DidCheckForMoreWork] where DidCheckForMoreWork
// detects if another thread called OnWorkRequested.
//
// Nesting is assumed to be dealt with by the ThreadController.
//
// Most methods are thread-affine except for On(Delayed)WorkRequested which are
// is thread-safe.
class BASE_EXPORT WorkDeduplicator {
public:
// Creates an unbound WorkDeduplicator. BindToCurrentThread must be called
// before work can be scheduled.
explicit WorkDeduplicator(
scoped_refptr<const AssociatedThreadId> associated_thread);
~WorkDeduplicator();
enum ShouldScheduleWork {
kScheduleImmediate,
kNotNeeded,
};
// Returns ShouldScheduleWork::kSchedule if OnWorkRequested was called while
// unbound. Must be called on the associated thread.
ShouldScheduleWork BindToCurrentThread();
// Returns true if it's OK to schedule a DoWork without risk of task
// duplication. Returns false if:
// * We are unbound
// * We are in a DoWork
// * There is a pending DoWork
//
// Otherwise sets the pending DoWork flag and returns true.
// Can be called on any thread.
//
// DoWork
// ---------------------------------------------------------------------
// | <- OnWorkStarted | |
// | WillCheckForMoreWork -> | |
// | | DidCheckForMoreWork -> |
// ---------------------------------------------------------------------
// ^ ^ ^ ^
// | | | |
// A B C D
//
// Consider a DoWork and calls to OnWorkRequested at various times:
// A: return ShouldScheduleWork::kNotNeeded because there's a pending DoWork.
// B: return ShouldScheduleWork::kNotNeeded because we're in a DoWork.
// C: return ShouldScheduleWork::kNotNeeded because we're in a DoWork, however
// DidCheckForMoreWork should subsequently return
// ShouldScheduleWork::kScheduleImmediate.
// D: If DidCheckForMoreWork(NextTask::kIsImmediate) was called then it
// should ShouldScheduleWork::kNotNeeded because there's a pending DoWork.
// Otherwise it should return ShouldScheduleWork::kScheduleImmediate, but a
// subsequent call to OnWorkRequested should return
// ShouldScheduleWork::kNotNeeded because there's now a pending DoWork.
ShouldScheduleWork OnWorkRequested();
// Returns ShouldScheduleWork::kScheduleImmediate if it's OK to schedule a
// DoDelayedWork without risk of redundancy. Deduplication of delayed work is
// assumed to have been done by the caller, the purpose of this method it to
// check if there's a pending DoWork which would schedule a delayed
// continuation as needed.
//
// Returns ShouldScheduleWork::kNotNeeded if:
// * We are unbound
// * We are in a DoWork
// * There is a pending DoWork
//
// Must be called on the associated thread.
ShouldScheduleWork OnDelayedWorkRequested() const;
// Marks us as having entered a DoWork, clearing the pending DoWork flag.
// Must be called on the associated thread.
void OnWorkStarted();
// Marks us as being about to check if we have more work. This notification
// helps prevent DoWork duplication in two scenarios:
// * A cross-thread immediate task is posted while we are running a task. If
// the TaskQueue is disabled we can avoid a potentially spurious DoWork.
// * A task is run which posts an immediate task but the ThreadControllerImpl
// work batch size is 2, and there's no further work. The immediate task ran
// in the work batch so we don't need another DoWork.
void WillCheckForMoreWork();
enum NextTask {
kIsImmediate,
kIsDelayed,
};
// Marks us as exiting DoWork. Returns ShouldScheduleWork::kScheduleImmediate
// if an immediate DoWork continuation should be posted. This method
// atomically takes into account any OnWorkRequested's called between
// gathering information about |next_task| and this call. Must be called on
// the associated thread.
ShouldScheduleWork DidCheckForMoreWork(NextTask next_task);
private:
enum Flags {
kInDoWorkFlag = 1 << 0,
kPendingDoWorkFlag = 1 << 1,
kBoundFlag = 1 << 2,
};
enum State {
kUnbound = 0,
kIdle = Flags::kBoundFlag,
kDoWorkPending = Flags::kPendingDoWorkFlag | Flags::kBoundFlag,
kInDoWork = Flags::kInDoWorkFlag | Flags::kBoundFlag,
};
std::atomic<int> state_{State::kUnbound};
const scoped_refptr<const AssociatedThreadId> associated_thread_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_DEDUPLICATOR_H_

View File

@@ -0,0 +1,331 @@
// 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 <optional>
#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/container/inlined_vector.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();
}
std::optional<TaskOrder> WorkQueue::GetFrontTaskOrder() const {
if (tasks_.empty() || BlockedByFence())
return std::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|.
absl::InlinedVector<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_ = std::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

View File

@@ -0,0 +1,188 @@
// 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.
#ifndef BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_H_
#define BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_H_
#include <optional>
#include "base/base_export.h"
#include "base/containers/intrusive_heap.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/task/sequence_manager/fence.h"
#include "base/task/sequence_manager/sequenced_task_source.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/values.h"
namespace base {
namespace sequence_manager {
class TaskOrder;
namespace internal {
class WorkQueueSets;
// This class keeps track of immediate and delayed tasks which are due to run
// now. It interfaces deeply with WorkQueueSets which keeps track of which queue
// (with a given priority) contains the oldest task.
//
// If a fence is inserted, WorkQueue behaves normally up until
// TakeTaskFromWorkQueue reaches or exceeds the fence. At that point it the
// API subset used by WorkQueueSets pretends the WorkQueue is empty until the
// fence is removed. This functionality is a primitive intended for use by
// throttling mechanisms.
class BASE_EXPORT WorkQueue {
public:
using QueueType = internal::TaskQueueImpl::WorkQueueType;
// Note |task_queue| can be null if queue_type is kNonNestable.
WorkQueue(TaskQueueImpl* task_queue, const char* name, QueueType queue_type);
WorkQueue(const WorkQueue&) = delete;
WorkQueue& operator=(const WorkQueue&) = delete;
~WorkQueue();
// Associates this work queue with the given work queue sets. This must be
// called before any tasks can be inserted into this work queue.
void AssignToWorkQueueSets(WorkQueueSets* work_queue_sets);
// Assigns the current set index.
void AssignSetIndex(size_t work_queue_set_index);
Value::List AsValue(TimeTicks now) const;
// Returns true if the |tasks_| is empty. This method ignores any fences.
bool Empty() const { return tasks_.empty(); }
// Returns the front task's TaskOrder if `tasks_` is non-empty and a fence
// hasn't been reached, otherwise returns nullopt.
std::optional<TaskOrder> GetFrontTaskOrder() const;
// Returns the first task in this queue or null if the queue is empty. This
// method ignores any fences.
const Task* GetFrontTask() const;
// Returns the last task in this queue or null if the queue is empty. This
// method ignores any fences.
const Task* GetBackTask() const;
// Pushes the task onto the |tasks_| and if a fence hasn't been reached
// it informs the WorkQueueSets if the head changed.
void Push(Task task);
// RAII helper that helps efficiently push N Tasks to a WorkQueue.
class BASE_EXPORT TaskPusher {
public:
TaskPusher(const TaskPusher&) = delete;
TaskPusher(TaskPusher&& other);
~TaskPusher();
void Push(Task task);
private:
friend class WorkQueue;
explicit TaskPusher(WorkQueue* work_queue);
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of sampling
// profiler data and tab_search:top100:2020).
RAW_PTR_EXCLUSION WorkQueue* work_queue_ = nullptr;
const bool was_empty_;
};
// Returns an RAII helper to efficiently push multiple tasks.
TaskPusher CreateTaskPusher();
// Pushes the task onto the front of the |tasks_| and if it's before any
// fence it informs the WorkQueueSets the head changed. Use with caution this
// API can easily lead to task starvation if misused.
void PushNonNestableTaskToFront(Task task);
// Reloads the empty |tasks_| with
// |task_queue_->TakeImmediateIncomingQueue| and if a fence hasn't been
// reached it informs the WorkQueueSets if the head changed.
void TakeImmediateIncomingQueueTasks();
size_t Size() const { return tasks_.size(); }
size_t Capacity() const { return tasks_.capacity(); }
// Pulls a task off the |tasks_| and informs the WorkQueueSets. If the
// task removed had an enqueue order >= the current fence then WorkQueue
// pretends to be empty as far as the WorkQueueSets is concerned.
Task TakeTaskFromWorkQueue();
// Removes all canceled tasks from the head of the list. Returns true if any
// tasks were removed.
bool RemoveAllCanceledTasksFromFront();
const char* name() const { return name_; }
TaskQueueImpl* task_queue() const { return task_queue_; }
WorkQueueSets* work_queue_sets() const { return work_queue_sets_; }
size_t work_queue_set_index() const { return work_queue_set_index_; }
HeapHandle heap_handle() const { return heap_handle_; }
void set_heap_handle(HeapHandle handle) { heap_handle_ = handle; }
QueueType queue_type() const { return queue_type_; }
// Submit a fence. When TakeTaskFromWorkQueue encounters a task whose
// enqueue_order is >= |fence| then the WorkQueue will start pretending to be.
// empty.
// Inserting a fence may supersede a previous one and unblock some tasks.
// Returns true if any tasks where unblocked, returns false otherwise.
bool InsertFence(Fence fence);
// Submit a fence without triggering a WorkQueueSets notification.
// Caller must ensure that WorkQueueSets are properly updated.
// This method should not be called when a fence is already present.
void InsertFenceSilently(Fence fence);
// Removes any fences that where added and if WorkQueue was pretending to be
// empty, then the real value is reported to WorkQueueSets. Returns true if
// any tasks where unblocked.
bool RemoveFence();
// Returns true if any tasks are blocked by the fence. Returns true if the
// queue is empty and fence has been set (i.e. future tasks would be blocked).
// Otherwise returns false.
bool BlockedByFence() const;
// Shrinks |tasks_| if it's wasting memory.
void MaybeShrinkQueue();
// Test support function. This should not be used in production code.
void PopTaskForTesting();
// Iterates through |tasks_| adding any that are older than |reference| to
// |result|.
void CollectTasksOlderThan(TaskOrder reference,
std::vector<const Task*>* result) const;
bool InsertFenceImpl(Fence fence);
TaskQueueImpl::TaskDeque tasks_;
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of speedometer3).
RAW_PTR_EXCLUSION WorkQueueSets* work_queue_sets_ = nullptr; // NOT OWNED.
RAW_PTR_EXCLUSION TaskQueueImpl* const task_queue_ = nullptr; // NOT OWNED.
size_t work_queue_set_index_ = 0;
// Iff the queue isn't empty (or appearing to be empty due to a fence) then
// |heap_handle_| will be valid and correspond to this queue's location within
// an IntrusiveHeap inside the WorkQueueSet.
HeapHandle heap_handle_;
const char* const name_;
std::optional<Fence> fence_;
const QueueType queue_type_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_H_

View File

@@ -0,0 +1,232 @@
// 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_sets.h"
#include <optional>
#include "base/check_op.h"
#include "base/task/sequence_manager/task_order.h"
#include "base/task/sequence_manager/work_queue.h"
namespace base {
namespace sequence_manager {
namespace internal {
WorkQueueSets::WorkQueueSets(const char* name,
Observer* observer,
const SequenceManager::Settings& settings)
: name_(name),
work_queue_heaps_(settings.priority_settings.priority_count()),
#if DCHECK_IS_ON()
last_rand_(settings.random_task_selection_seed),
#endif
observer_(observer) {
}
WorkQueueSets::~WorkQueueSets() = default;
void WorkQueueSets::AddQueue(WorkQueue* work_queue, size_t set_index) {
DCHECK(!work_queue->work_queue_sets());
DCHECK_LT(set_index, work_queue_heaps_.size());
DCHECK(!work_queue->heap_handle().IsValid());
std::optional<TaskOrder> key = work_queue->GetFrontTaskOrder();
work_queue->AssignToWorkQueueSets(this);
work_queue->AssignSetIndex(set_index);
if (!key)
return;
bool was_empty = work_queue_heaps_[set_index].empty();
work_queue_heaps_[set_index].insert({*key, work_queue});
if (was_empty)
observer_->WorkQueueSetBecameNonEmpty(set_index);
}
void WorkQueueSets::RemoveQueue(WorkQueue* work_queue) {
DCHECK_EQ(this, work_queue->work_queue_sets());
work_queue->AssignToWorkQueueSets(nullptr);
if (!work_queue->heap_handle().IsValid())
return;
size_t set_index = work_queue->work_queue_set_index();
DCHECK_LT(set_index, work_queue_heaps_.size());
work_queue_heaps_[set_index].erase(work_queue->heap_handle());
if (work_queue_heaps_[set_index].empty())
observer_->WorkQueueSetBecameEmpty(set_index);
DCHECK(!work_queue->heap_handle().IsValid());
}
void WorkQueueSets::ChangeSetIndex(WorkQueue* work_queue, size_t set_index) {
DCHECK_EQ(this, work_queue->work_queue_sets());
DCHECK_LT(set_index, work_queue_heaps_.size());
std::optional<TaskOrder> key = work_queue->GetFrontTaskOrder();
size_t old_set = work_queue->work_queue_set_index();
DCHECK_LT(old_set, work_queue_heaps_.size());
DCHECK_NE(old_set, set_index);
work_queue->AssignSetIndex(set_index);
DCHECK_EQ(key.has_value(), work_queue->heap_handle().IsValid());
if (!key)
return;
work_queue_heaps_[old_set].erase(work_queue->heap_handle());
bool was_empty = work_queue_heaps_[set_index].empty();
work_queue_heaps_[set_index].insert({*key, work_queue});
// Invoke `WorkQueueSetBecameNonEmpty()` before `WorkQueueSetBecameEmpty()` so
// `observer_` doesn't momentarily observe that all work queue sets are empty.
// TaskQueueSelectorTest.TestDisableEnable will fail if the order changes.
if (was_empty)
observer_->WorkQueueSetBecameNonEmpty(set_index);
if (work_queue_heaps_[old_set].empty()) {
observer_->WorkQueueSetBecameEmpty(old_set);
}
}
void WorkQueueSets::OnQueuesFrontTaskChanged(WorkQueue* work_queue) {
size_t set_index = work_queue->work_queue_set_index();
DCHECK_EQ(this, work_queue->work_queue_sets());
DCHECK_LT(set_index, work_queue_heaps_.size());
DCHECK(work_queue->heap_handle().IsValid());
DCHECK(!work_queue_heaps_[set_index].empty()) << " set_index = " << set_index;
if (auto key = work_queue->GetFrontTaskOrder()) {
// O(log n)
work_queue_heaps_[set_index].Replace(work_queue->heap_handle(),
{*key, work_queue});
} else {
// O(log n)
work_queue_heaps_[set_index].erase(work_queue->heap_handle());
DCHECK(!work_queue->heap_handle().IsValid());
if (work_queue_heaps_[set_index].empty())
observer_->WorkQueueSetBecameEmpty(set_index);
}
}
void WorkQueueSets::OnTaskPushedToEmptyQueue(WorkQueue* work_queue) {
// NOTE if this function changes, we need to keep |WorkQueueSets::AddQueue| in
// sync.
DCHECK_EQ(this, work_queue->work_queue_sets());
std::optional<TaskOrder> key = work_queue->GetFrontTaskOrder();
DCHECK(key);
size_t set_index = work_queue->work_queue_set_index();
DCHECK_LT(set_index, work_queue_heaps_.size())
<< " set_index = " << set_index;
// |work_queue| should not be in work_queue_heaps_[set_index].
DCHECK(!work_queue->heap_handle().IsValid());
bool was_empty = work_queue_heaps_[set_index].empty();
work_queue_heaps_[set_index].insert({*key, work_queue});
if (was_empty)
observer_->WorkQueueSetBecameNonEmpty(set_index);
}
void WorkQueueSets::OnPopMinQueueInSet(WorkQueue* work_queue) {
// Assume that `work_queue` contains the lowest `TaskOrder`.
size_t set_index = work_queue->work_queue_set_index();
DCHECK_EQ(this, work_queue->work_queue_sets());
DCHECK_LT(set_index, work_queue_heaps_.size());
DCHECK(!work_queue_heaps_[set_index].empty()) << " set_index = " << set_index;
DCHECK_EQ(work_queue_heaps_[set_index].top().value, work_queue)
<< " set_index = " << set_index;
DCHECK(work_queue->heap_handle().IsValid());
if (auto key = work_queue->GetFrontTaskOrder()) {
// O(log n)
work_queue_heaps_[set_index].ReplaceTop({*key, work_queue});
} else {
// O(log n)
work_queue_heaps_[set_index].pop();
DCHECK(!work_queue->heap_handle().IsValid());
DCHECK(work_queue_heaps_[set_index].empty() ||
work_queue_heaps_[set_index].top().value != work_queue);
if (work_queue_heaps_[set_index].empty()) {
observer_->WorkQueueSetBecameEmpty(set_index);
}
}
}
void WorkQueueSets::OnQueueBlocked(WorkQueue* work_queue) {
DCHECK_EQ(this, work_queue->work_queue_sets());
HeapHandle heap_handle = work_queue->heap_handle();
if (!heap_handle.IsValid())
return;
size_t set_index = work_queue->work_queue_set_index();
DCHECK_LT(set_index, work_queue_heaps_.size());
work_queue_heaps_[set_index].erase(heap_handle);
if (work_queue_heaps_[set_index].empty())
observer_->WorkQueueSetBecameEmpty(set_index);
}
std::optional<WorkQueueAndTaskOrder>
WorkQueueSets::GetOldestQueueAndTaskOrderInSet(size_t set_index) const {
DCHECK_LT(set_index, work_queue_heaps_.size());
if (work_queue_heaps_[set_index].empty())
return std::nullopt;
const OldestTaskOrder& oldest = work_queue_heaps_[set_index].top();
DCHECK(oldest.value->heap_handle().IsValid());
#if DCHECK_IS_ON()
std::optional<TaskOrder> order = oldest.value->GetFrontTaskOrder();
DCHECK(order && oldest.key == *order);
#endif
return WorkQueueAndTaskOrder(*oldest.value, oldest.key);
}
#if DCHECK_IS_ON()
std::optional<WorkQueueAndTaskOrder>
WorkQueueSets::GetRandomQueueAndTaskOrderInSet(size_t set_index) const {
DCHECK_LT(set_index, work_queue_heaps_.size());
if (work_queue_heaps_[set_index].empty())
return std::nullopt;
const OldestTaskOrder& chosen =
work_queue_heaps_[set_index].begin()[static_cast<long>(
Random() % work_queue_heaps_[set_index].size())];
#if DCHECK_IS_ON()
std::optional<TaskOrder> key = chosen.value->GetFrontTaskOrder();
DCHECK(key && chosen.key == *key);
#endif
return WorkQueueAndTaskOrder(*chosen.value, chosen.key);
}
#endif
bool WorkQueueSets::IsSetEmpty(size_t set_index) const {
DCHECK_LT(set_index, work_queue_heaps_.size())
<< " set_index = " << set_index;
return work_queue_heaps_[set_index].empty();
}
#if DCHECK_IS_ON() || !defined(NDEBUG)
bool WorkQueueSets::ContainsWorkQueueForTest(
const WorkQueue* work_queue) const {
std::optional<TaskOrder> task_order = work_queue->GetFrontTaskOrder();
for (const auto& heap : work_queue_heaps_) {
for (const OldestTaskOrder& heap_value_pair : heap) {
if (heap_value_pair.value == work_queue) {
DCHECK(task_order);
DCHECK(heap_value_pair.key == *task_order);
DCHECK_EQ(this, work_queue->work_queue_sets());
return true;
}
}
}
if (work_queue->work_queue_sets() == this) {
DCHECK(!task_order);
return true;
}
return false;
}
#endif
void WorkQueueSets::CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const {
std::optional<TaskOrder> task_order =
selected_work_queue->GetFrontTaskOrder();
CHECK(task_order);
for (size_t priority = selected_work_queue->work_queue_set_index() + 1;
priority < work_queue_heaps_.size(); priority++) {
for (const OldestTaskOrder& pair : work_queue_heaps_[priority]) {
pair.value->CollectTasksOlderThan(*task_order, result);
}
}
}
} // namespace internal
} // namespace sequence_manager
} // namespace base

View File

@@ -0,0 +1,161 @@
// 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.
#ifndef BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_SETS_H_
#define BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_SETS_H_
#include <functional>
#include <optional>
#include <vector>
#include "base/base_export.h"
#include "base/containers/intrusive_heap.h"
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/stack_allocated.h"
#include "base/task/sequence_manager/sequence_manager.h"
#include "base/task/sequence_manager/task_order.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/task/sequence_manager/work_queue.h"
namespace base {
namespace sequence_manager {
namespace internal {
struct WorkQueueAndTaskOrder {
STACK_ALLOCATED();
public:
WorkQueueAndTaskOrder(WorkQueue& work_queue, const TaskOrder& task_order)
: queue(&work_queue), order(task_order) {}
WorkQueue* queue = nullptr;
TaskOrder order;
};
// There is a min-heap for each scheduler priority which keeps track of which
// queue in the set has the oldest task (i.e. the one that should be run next if
// the TaskQueueSelector chooses to run a task a given priority).
class BASE_EXPORT WorkQueueSets {
public:
class Observer {
public:
virtual ~Observer() = default;
virtual void WorkQueueSetBecameEmpty(size_t set_index) = 0;
virtual void WorkQueueSetBecameNonEmpty(size_t set_index) = 0;
};
WorkQueueSets(const char* name,
Observer* observer,
const SequenceManager::Settings& settings);
WorkQueueSets(const WorkQueueSets&) = delete;
WorkQueueSets& operator=(const WorkQueueSets&) = delete;
~WorkQueueSets();
// O(log num queues)
void AddQueue(WorkQueue* queue, size_t set_index);
// O(log num queues)
void RemoveQueue(WorkQueue* work_queue);
// O(log num queues)
void ChangeSetIndex(WorkQueue* queue, size_t set_index);
// O(log num queues)
void OnQueuesFrontTaskChanged(WorkQueue* queue);
// O(log num queues)
void OnTaskPushedToEmptyQueue(WorkQueue* work_queue);
// If empty it's O(1) amortized, otherwise it's O(log num queues). Slightly
// faster on average than OnQueuesFrontTaskChanged.
// Assumes |work_queue| contains the lowest enqueue order in the set.
void OnPopMinQueueInSet(WorkQueue* work_queue);
// O(log num queues)
void OnQueueBlocked(WorkQueue* work_queue);
// O(1)
std::optional<WorkQueueAndTaskOrder> GetOldestQueueAndTaskOrderInSet(
size_t set_index) const;
#if DCHECK_IS_ON()
// O(1)
std::optional<WorkQueueAndTaskOrder> GetRandomQueueAndTaskOrderInSet(
size_t set_index) const;
#endif
// O(1)
bool IsSetEmpty(size_t set_index) const;
#if DCHECK_IS_ON() || !defined(NDEBUG)
// Note this iterates over everything in |work_queue_heaps_|.
// It's intended for use with DCHECKS and for testing
bool ContainsWorkQueueForTest(const WorkQueue* queue) const;
#endif
const char* GetName() const { return name_; }
// Collects ready tasks which where skipped over when |selected_work_queue|
// was selected. Note this is somewhat expensive.
void CollectSkippedOverLowerPriorityTasks(
const internal::WorkQueue* selected_work_queue,
std::vector<const Task*>* result) const;
private:
struct OldestTaskOrder {
TaskOrder key;
// RAW_PTR_EXCLUSION: Performance: visible in sampling profiler stacks.
RAW_PTR_EXCLUSION WorkQueue* value = nullptr;
// Used for a min-heap.
bool operator>(const OldestTaskOrder& other) const {
return key > other.key;
}
void SetHeapHandle(HeapHandle handle) { value->set_heap_handle(handle); }
void ClearHeapHandle() { value->set_heap_handle(HeapHandle()); }
HeapHandle GetHeapHandle() const { return value->heap_handle(); }
};
const char* const name_;
// For each set |work_queue_heaps_| has a queue of WorkQueue ordered by the
// oldest task in each WorkQueue.
std::vector<IntrusiveHeap<OldestTaskOrder, std::greater<>>> work_queue_heaps_;
#if DCHECK_IS_ON()
static inline uint64_t MurmurHash3(uint64_t value) {
value ^= value >> 33;
value *= uint64_t{0xFF51AFD7ED558CCD};
value ^= value >> 33;
value *= uint64_t{0xC4CEB9FE1A85EC53};
value ^= value >> 33;
return value;
}
// This is for a debugging feature which lets us randomize task selection. Its
// not for production use.
// TODO(crbug.com/40234060): Use a seedable PRNG from ::base if one is added.
uint64_t Random() const {
last_rand_ = MurmurHash3(last_rand_);
return last_rand_;
}
mutable uint64_t last_rand_;
#endif
const raw_ptr<Observer> observer_;
};
} // namespace internal
} // namespace sequence_manager
} // namespace base
#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_SETS_H_

View File

@@ -0,0 +1,142 @@
// Copyright 2023 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_tracker.h"
#include "base/check.h"
#include "base/task/common/scoped_defer_task_posting.h"
#include "base/threading/thread_restrictions.h"
namespace base::sequence_manager::internal {
SyncWorkAuthorization::SyncWorkAuthorization(SyncWorkAuthorization&& other)
: tracker_(other.tracker_) {
other.tracker_ = nullptr;
}
SyncWorkAuthorization& SyncWorkAuthorization::operator=(
SyncWorkAuthorization&& other) {
tracker_ = other.tracker_;
other.tracker_ = nullptr;
return *this;
}
SyncWorkAuthorization::~SyncWorkAuthorization() {
if (!tracker_) {
return;
}
{
base::internal::CheckedAutoLock auto_lock(tracker_->active_sync_work_lock_);
uint32_t prev = tracker_->state_.fetch_and(
~WorkTracker::kActiveSyncWork, WorkTracker::kMemoryReleaseAllowWork);
DCHECK(prev & WorkTracker::kActiveSyncWork);
}
tracker_->active_sync_work_cv_.Signal();
}
SyncWorkAuthorization::SyncWorkAuthorization(WorkTracker* state)
: tracker_(state) {}
WorkTracker::WorkTracker() {
DETACH_FROM_THREAD(thread_checker_);
}
WorkTracker::~WorkTracker() = default;
void WorkTracker::SetRunTaskSynchronouslyAllowed(
bool can_run_tasks_synchronously) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (can_run_tasks_synchronously) {
state_.fetch_or(kSyncWorkSupported, kMemoryReleaseAllowWork);
} else {
// After this returns, non-sync work may run without being tracked by
// `this`. Ensures that such work is correctly sequenced with sync work by:
// - Waiting until sync work is complete.
// - Acquiring memory written by sync work (`kMemoryAcquireBeforeWork` here
// is paired with `kMemoryReleaseAllowWork` in `~SyncWorkAuthorization`).
uint32_t prev =
state_.fetch_and(~kSyncWorkSupported, kMemoryAcquireBeforeWork);
if (prev & kActiveSyncWork) {
WaitNoSyncWork();
}
}
}
void WorkTracker::WaitNoSyncWork() {
// Do not process new PostTasks, defer them. Tracing can call PostTask, but
// it will try to grab locks that are not allowed here.
ScopedDeferTaskPosting disallow_task_posting;
ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow;
// `std::memory_order_relaxed` instead of `kMemoryAcquireBeforeWork` because
// the lock implicitly acquires memory released by `~SyncWorkAuthorization`.
base::internal::CheckedAutoLock auto_lock(active_sync_work_lock_);
uint32_t prev = state_.load(std::memory_order_relaxed);
while (prev & kActiveSyncWork) {
active_sync_work_cv_.Wait();
prev = state_.load(std::memory_order_relaxed);
}
}
void WorkTracker::WillRequestReloadImmediateWorkQueue() {
// May be called from any thread.
// Sync work is disallowed until `WillReloadImmediateWorkQueues()` and
// `OnIdle()` are called.
state_.fetch_or(kImmediateWorkQueueNeedsReload,
kMemoryRelaxedNotAllowOrBeforeWork);
}
void WorkTracker::WillReloadImmediateWorkQueues() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Sync work is disallowed until `OnIdle()` is called.
state_.fetch_and(
~(kImmediateWorkQueueNeedsReload | kWorkQueuesEmptyAndNoWorkRunning),
kMemoryRelaxedNotAllowOrBeforeWork);
}
void WorkTracker::OnBeginWork() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
uint32_t prev = state_.fetch_and(~kWorkQueuesEmptyAndNoWorkRunning,
kMemoryAcquireBeforeWork);
if (prev & kActiveSyncWork) {
DCHECK(prev & kSyncWorkSupported);
WaitNoSyncWork();
}
}
void WorkTracker::OnIdle() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// This may allow sync work. "release" so that sync work that runs after this
// sees all writes issued by previous sequenced work.
state_.fetch_or(kWorkQueuesEmptyAndNoWorkRunning, std::memory_order_release);
}
SyncWorkAuthorization WorkTracker::TryAcquireSyncWorkAuthorization() {
// May be called from any thread.
uint32_t state = state_.load(std::memory_order_relaxed);
// "acquire" so that sync work sees writes issued by sequenced work that
// precedes it.
if (state == (kSyncWorkSupported | kWorkQueuesEmptyAndNoWorkRunning) &&
state_.compare_exchange_strong(state, state | kActiveSyncWork,
std::memory_order_acquire,
std::memory_order_relaxed)) {
return SyncWorkAuthorization(this);
}
return SyncWorkAuthorization(nullptr);
}
void WorkTracker::AssertHasWork() {
CHECK(!(state_.load(std::memory_order_relaxed) &
kWorkQueuesEmptyAndNoWorkRunning));
}
} // namespace base::sequence_manager::internal

View File

@@ -0,0 +1,134 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCE_MANAGER_WORK_TRACKER_H_
#define BASE_TASK_SEQUENCE_MANAGER_WORK_TRACKER_H_
#include <atomic>
#include <cstdint>
#include "base/base_export.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/synchronization/condition_variable.h"
#include "base/task/common/checked_lock.h"
#include "base/threading/thread_checker.h"
namespace base::sequence_manager::internal {
class WorkTracker;
// When `IsValid()`, this represents an authorization to execute work
// synchronously inside `RunOrPostTask`.
class BASE_EXPORT SyncWorkAuthorization {
public:
SyncWorkAuthorization(SyncWorkAuthorization&&);
SyncWorkAuthorization& operator=(SyncWorkAuthorization&&);
~SyncWorkAuthorization();
bool IsValid() const { return !!tracker_; }
private:
friend class WorkTracker;
explicit SyncWorkAuthorization(WorkTracker* state);
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of speedometer3).
RAW_PTR_EXCLUSION WorkTracker* tracker_ = nullptr;
};
// Tracks queued and running work to support `RunOrPostTask`.
class BASE_EXPORT WorkTracker {
public:
WorkTracker();
~WorkTracker();
// Controls whether `RunOrPostTask()` can run its callback synchronously when
// no work is tracked by this. Don't allow this when work that is sequenced
// with `RunOrPostTask()` may run without being tracked by methods below.
void SetRunTaskSynchronouslyAllowed(bool can_run_tasks_synchronously);
// Invoked before requesting to reload an empty immediate work queue. After
// this, `RunOrPostTask()` can't run tasks synchronously until
// `WillReloadImmediateWorkQueues()` and `OnIdle()` have been called in
// sequence.
void WillRequestReloadImmediateWorkQueue();
// Invoked before reloading empty immediate work queues.
void WillReloadImmediateWorkQueues();
// Invoked before doing work. After this `RunOrPostTask()` can't run tasks
// until `OnIdle()` is called. Work may begin even if immediate work queues
// haven't be reloaded since the last `OnIdle()`, e.g. when a task queue is
// enabled, when tasks are moved from the delayed incoming queue to the
// delayed work queue or when the pump performs internal work.
void OnBeginWork();
// Invoked when the thread is out of work.
void OnIdle();
// Returns a valid `SyncWorkAuthorization` iff all these conditions are true:
// - Explicitly allowed by `SetRunTaskSynchronouslyAllowed()`
// - `WillReloadImmediateWorkQueues()` and `OnIdle()` were called in
// sequence after the last call to `WillRequestReloadImmediateWorkQueue()`
// - `OnIdle()` was called after the last call to `OnBeginWork()`
SyncWorkAuthorization TryAcquireSyncWorkAuthorization();
// Asserts that there is work tracked by this, i.e.
// `TryAcquireSyncWorkAuthorization()` would not grant a sync work
// authorization even if allowed by `SetRunTaskSynchronouslyAllowed()`.
void AssertHasWork();
private:
friend class SyncWorkAuthorization;
void WaitNoSyncWork();
// An atomic variable to track:
// - Whether there is an unfulfilled request to reload immediate work queues.
static constexpr uint32_t kImmediateWorkQueueNeedsReload = 1 << 0;
// - Whether all work queues are empty and no work is running.
static constexpr uint32_t kWorkQueuesEmptyAndNoWorkRunning = 1 << 1;
// - Whether a valid `SyncWorkAuthorization` exists.
static constexpr uint32_t kActiveSyncWork = 1 << 2;
// - Whether a valid `SyncWorkAuthorization` can be granted when no work is
// tracked by `this`.
static constexpr uint32_t kSyncWorkSupported = 1 << 3;
std::atomic_uint32_t state_{kWorkQueuesEmptyAndNoWorkRunning};
// Memory order for `state_`:
//
// Sync work must see all memory written before it was allowed. Similarly,
// non-sync work must see all memory written by sync work. As a result:
//
// Operations that may allow sync work are std::memory_order_release:
// - Set `kWorkQueuesEmptyAndNoWorkRunning`
// - Set `kSyncWorkSupported`
//
// Operations that may allow non-sync work are `std::memory_order_release`:
// - Clear `kActiveSyncWork`
//
// Operations that precede sync work are `std::memory_order_acquire`:
// - Set `kActiveSyncWork`
//
// Operations that precede non-sync work are `std::memory_order_acquire`:
// - Check that `kActiveSyncWork` is not set.
static constexpr std::memory_order kMemoryReleaseAllowWork =
std::memory_order_release;
static constexpr std::memory_order kMemoryAcquireBeforeWork =
std::memory_order_acquire;
static constexpr std::memory_order kMemoryRelaxedNotAllowOrBeforeWork =
std::memory_order_relaxed;
// Allows `OnBeginWork()` to wait until there is no more valid
// `SyncWorkAuthorization`.
base::internal::CheckedLock active_sync_work_lock_;
ConditionVariable active_sync_work_cv_ =
active_sync_work_lock_.CreateConditionVariable();
THREAD_CHECKER(thread_checker_);
};
} // namespace base::sequence_manager::internal
#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_TRACKER_H_

View File

@@ -0,0 +1,141 @@
// Copyright 2012 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/sequenced_task_runner.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/task/default_delayed_task_handle_delegate.h"
#include "base/time/time.h"
namespace base {
namespace {
constinit thread_local SequencedTaskRunner::CurrentDefaultHandle*
current_default_handle = nullptr;
} // namespace
bool SequencedTaskRunner::PostNonNestableTask(const Location& from_here,
OnceClosure task) {
return PostNonNestableDelayedTask(from_here, std::move(task),
base::TimeDelta());
}
DelayedTaskHandle SequencedTaskRunner::PostCancelableDelayedTask(
subtle::PostDelayedTaskPassKey,
const Location& from_here,
OnceClosure task,
TimeDelta delay) {
auto delayed_task_handle_delegate =
std::make_unique<DefaultDelayedTaskHandleDelegate>();
task = delayed_task_handle_delegate->BindCallback(std::move(task));
DelayedTaskHandle delayed_task_handle(
std::move(delayed_task_handle_delegate));
PostDelayedTask(from_here, std::move(task), delay);
return delayed_task_handle;
}
DelayedTaskHandle SequencedTaskRunner::PostCancelableDelayedTaskAt(
subtle::PostDelayedTaskPassKey pass_key,
const Location& from_here,
OnceClosure task,
TimeTicks delayed_run_time,
subtle::DelayPolicy deadline_policy) {
auto delayed_task_handle_delegate =
std::make_unique<DefaultDelayedTaskHandleDelegate>();
task = delayed_task_handle_delegate->BindCallback(std::move(task));
DelayedTaskHandle delayed_task_handle(
std::move(delayed_task_handle_delegate));
if (!PostDelayedTaskAt(pass_key, from_here, std::move(task), delayed_run_time,
deadline_policy)) {
DCHECK(!delayed_task_handle.IsValid());
}
return delayed_task_handle;
}
bool SequencedTaskRunner::PostDelayedTaskAt(
subtle::PostDelayedTaskPassKey,
const Location& from_here,
OnceClosure task,
TimeTicks delayed_run_time,
subtle::DelayPolicy deadline_policy) {
return PostDelayedTask(from_here, std::move(task),
delayed_run_time.is_null()
? base::TimeDelta()
: delayed_run_time - TimeTicks::Now());
}
bool SequencedTaskRunner::RunOrPostTask(subtle::RunOrPostTaskPassKey,
const Location& from_here,
OnceClosure task) {
return PostTask(from_here, std::move(task));
}
// static
const scoped_refptr<SequencedTaskRunner>&
SequencedTaskRunner::GetCurrentDefault() {
CHECK(HasCurrentDefault())
<< "Error: This caller requires a sequenced context (i.e. the current "
"task needs to run from a SequencedTaskRunner). If you're in a test "
"refer to //docs/threading_and_tasks_testing.md.";
return current_default_handle->task_runner_;
}
// static
bool SequencedTaskRunner::HasCurrentDefault() {
return !!current_default_handle && !!current_default_handle->task_runner_;
}
SequencedTaskRunner::CurrentDefaultHandle::CurrentDefaultHandle(
scoped_refptr<SequencedTaskRunner> task_runner)
: CurrentDefaultHandle(std::move(task_runner), MayAlreadyExist{}) {
CHECK(!previous_handle_ || !previous_handle_->task_runner_);
}
SequencedTaskRunner::CurrentDefaultHandle::~CurrentDefaultHandle() {
DCHECK_EQ(current_default_handle, this);
current_default_handle = previous_handle_;
}
SequencedTaskRunner::CurrentDefaultHandle::CurrentDefaultHandle(
scoped_refptr<SequencedTaskRunner> task_runner,
MayAlreadyExist)
: task_runner_(std::move(task_runner)),
previous_handle_(current_default_handle) {
// Support overriding the current default with a null task runner or a task
// runner that runs its tasks in the current sequence.
DCHECK(!task_runner_ || task_runner_->RunsTasksInCurrentSequence());
current_default_handle = this;
}
bool SequencedTaskRunner::DeleteOrReleaseSoonInternal(
const Location& from_here,
void (*deleter)(const void*),
const void* object) {
return PostNonNestableTask(from_here, BindOnce(deleter, object));
}
OnTaskRunnerDeleter::OnTaskRunnerDeleter(
scoped_refptr<SequencedTaskRunner> task_runner)
: task_runner_(std::move(task_runner)) {
}
OnTaskRunnerDeleter::~OnTaskRunnerDeleter() = default;
OnTaskRunnerDeleter::OnTaskRunnerDeleter(OnTaskRunnerDeleter&&) = default;
OnTaskRunnerDeleter& OnTaskRunnerDeleter::operator=(
OnTaskRunnerDeleter&&) = default;
} // namespace base

View File

@@ -0,0 +1,415 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_SEQUENCED_TASK_RUNNER_H_
#define BASE_TASK_SEQUENCED_TASK_RUNNER_H_
#include <memory>
#include "base/auto_reset.h"
#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/task/delay_policy.h"
#include "base/task/delayed_task_handle.h"
#include "base/task/sequenced_task_runner_helpers.h"
#include "base/task/task_runner.h"
#include "base/types/pass_key.h"
namespace blink {
class LowPrecisionTimer;
class ScriptedIdleTaskController;
class TimerBase;
class TimerBasedTickProvider;
class WebRtcTaskQueue;
}
namespace IPC {
class ChannelAssociatedGroupController;
} // namespace IPC
namespace media {
class AlsaPcmOutputStream;
class AlsaPcmInputStream;
class FakeAudioWorker;
} // namespace media
namespace viz {
class ExternalBeginFrameSourceWin;
} // namespace viz
namespace webrtc {
class ThreadWrapper;
} // namespace webrtc
namespace base {
namespace android {
class PreFreezeBackgroundMemoryTrimmer;
}
namespace internal {
class DelayTimerBase;
class DelayedTaskManager;
}
class DeadlineTimer;
class MetronomeTimer;
class SingleThreadTaskRunner;
class TimeDelta;
class TimeTicks;
namespace subtle {
// Restricts access to PostCancelableDelayedTask*() to authorized callers.
class PostDelayedTaskPassKey {
private:
// Avoid =default to disallow creation by uniform initialization.
PostDelayedTaskPassKey() = default;
friend class base::internal::DelayTimerBase;
friend class base::internal::DelayedTaskManager;
friend class base::DeadlineTimer;
friend class base::MetronomeTimer;
friend class blink::LowPrecisionTimer;
friend class blink::ScriptedIdleTaskController;
friend class blink::TimerBase;
friend class blink::TimerBasedTickProvider;
friend class blink::WebRtcTaskQueue;
friend class PostDelayedTaskPassKeyForTesting;
friend class webrtc::ThreadWrapper;
friend class media::AlsaPcmOutputStream;
friend class media::AlsaPcmInputStream;
friend class media::FakeAudioWorker;
#if BUILDFLAG(IS_ANDROID)
friend class base::android::PreFreezeBackgroundMemoryTrimmer;
#endif
};
// Restricts access to RunOrPostTask() to authorized callers.
class RunOrPostTaskPassKey {
private:
// Avoid =default to disallow creation by uniform initialization.
RunOrPostTaskPassKey() = default;
friend class IPC::ChannelAssociatedGroupController;
friend class RunOrPostTaskPassKeyForTesting;
friend class viz::ExternalBeginFrameSourceWin;
};
class PostDelayedTaskPassKeyForTesting : public PostDelayedTaskPassKey {};
class RunOrPostTaskPassKeyForTesting : public RunOrPostTaskPassKey {};
} // namespace subtle
// A SequencedTaskRunner is a subclass of TaskRunner that provides
// additional guarantees on the order that tasks are started, as well
// as guarantees on when tasks are in sequence, i.e. one task finishes
// before the other one starts.
//
// Summary
// -------
// Non-nested tasks with the same delay will run one by one in FIFO
// order.
//
// Detailed guarantees
// -------------------
//
// SequencedTaskRunner also adds additional methods for posting
// non-nestable tasks. In general, an implementation of TaskRunner
// may expose task-running methods which are themselves callable from
// within tasks. A non-nestable task is one that is guaranteed to not
// be run from within an already-running task. Conversely, a nestable
// task (the default) is a task that can be run from within an
// already-running task.
//
// The guarantees of SequencedTaskRunner are as follows:
//
// - Given two tasks T2 and T1, T2 will start after T1 starts if:
//
// * T2 is posted after T1; and
// * T2 has equal or higher delay than T1; and
// * T2 is non-nestable or T1 is nestable.
//
// - If T2 will start after T1 starts by the above guarantee, then
// T2 will start after T1 finishes and is destroyed if:
//
// * T2 is non-nestable, or
// * T1 doesn't call any task-running methods.
//
// - If T2 will start after T1 finishes by the above guarantee, then
// all memory changes in T1 and T1's destruction will be visible
// to T2.
//
// - If T2 runs nested within T1 via a call to the task-running
// method M, then all memory changes in T1 up to the call to M
// will be visible to T2, and all memory changes in T2 will be
// visible to T1 from the return from M.
//
// Note that SequencedTaskRunner does not guarantee that tasks are run
// on a single dedicated thread, although the above guarantees provide
// most (but not all) of the same guarantees. If you do need to
// guarantee that tasks are run on a single dedicated thread, see
// SingleThreadTaskRunner (in single_thread_task_runner.h).
//
// Some corollaries to the above guarantees, assuming the tasks in
// question don't call any task-running methods:
//
// - Tasks posted via PostTask are run in FIFO order.
//
// - Tasks posted via PostNonNestableTask are run in FIFO order.
//
// - Tasks posted with the same delay and the same nestable state
// are run in FIFO order.
//
// - A list of tasks with the same nestable state posted in order of
// non-decreasing delay is run in FIFO order.
//
// - A list of tasks posted in order of non-decreasing delay with at
// most a single change in nestable state from nestable to
// non-nestable is run in FIFO order. (This is equivalent to the
// statement of the first guarantee above.)
//
// Some theoretical implementations of SequencedTaskRunner:
//
// - A SequencedTaskRunner that wraps a regular TaskRunner but makes
// sure that only one task at a time is posted to the TaskRunner,
// with appropriate memory barriers in between tasks.
//
// - A SequencedTaskRunner that, for each task, spawns a joinable
// thread to run that task and immediately quit, and then
// immediately joins that thread.
//
// - A SequencedTaskRunner that stores the list of posted tasks and
// has a method Run() that runs each runnable task in FIFO order
// that can be called from any thread, but only if another
// (non-nested) Run() call isn't already happening.
//
// SequencedTaskRunner::GetCurrentDefault() can be used while running
// a task to retrieve the default SequencedTaskRunner for the current
// sequence.
class BASE_EXPORT SequencedTaskRunner : public TaskRunner {
public:
// The two PostNonNestable*Task methods below are like their
// nestable equivalents in TaskRunner, but they guarantee that the
// posted task will not run nested within an already-running task.
//
// A simple corollary is that posting a task as non-nestable can
// only delay when the task gets run. That is, posting a task as
// non-nestable may not affect when the task gets run, or it could
// make it run later than it normally would, but it won't make it
// run earlier than it normally would.
// TODO(akalin): Get rid of the boolean return value for the methods
// below.
bool PostNonNestableTask(const Location& from_here, OnceClosure task);
virtual bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
base::TimeDelta delay) = 0;
// Posts the given |task| to be run only after |delay| has passed. Returns a
// handle that can be used to cancel the task. This should not be used
// directly. Consider using higher level timer primitives in
// base/timer/timer.h.
//
// The handle is only guaranteed valid while the task is pending execution.
// This means that it may be invalid if the posting failed, and will be
// invalid while the task is executing. Calling CancelTask() on an invalid
// handle is a no-op.
//
// This method and the handle it returns are not thread-safe and can only be
// used from the sequence this task runner runs its tasks on.
virtual DelayedTaskHandle PostCancelableDelayedTask(
subtle::PostDelayedTaskPassKey,
const Location& from_here,
OnceClosure task,
TimeDelta delay);
// Posts the given |task| to be run at |delayed_run_time| (or immediately if
// in the past), following |delay_policy|. Returns a handle that can be used
// to cancel the task. This should not be used directly. Consider using higher
// level timer primitives in base/timer/timer.h.
[[nodiscard]] virtual DelayedTaskHandle PostCancelableDelayedTaskAt(
subtle::PostDelayedTaskPassKey,
const Location& from_here,
OnceClosure task,
TimeTicks delayed_run_time,
subtle::DelayPolicy delay_policy);
// Posts the given |task| to be run at |delayed_run_time| (or immediately if
// in the past), following |delay_policy|. This is used by the default
// implementation of PostCancelableDelayedTaskAt(). The default behavior
// subtracts TimeTicks::Now() from |delayed_run_time| to get a delay. See
// base::Timer to post precise/repeating timeouts.
// TODO(crbug.com/40158967): Make pure virtual once all SequencedTaskRunners
// implement this.
virtual bool PostDelayedTaskAt(subtle::PostDelayedTaskPassKey,
const Location& from_here,
OnceClosure task,
TimeTicks delayed_run_time,
subtle::DelayPolicy delay_policy);
// May run `task` synchronously if no work that has ordering or mutual
// exclusion expectations with tasks from this `SequencedTaskRunner` is
// pending or running (if such work arrives after `task` starts running
// synchronously, it waits until `task` finishes). Otherwise, behaves like
// `PostTask`. Since `task` may run synchronously, it is generally not
// appropriate to invoke this if `task` may take a long time to run.
//
// TODO(crbug.com/40944462): This API is still in development. It doesn't yet
// support SequenceLocalStorage.
virtual bool RunOrPostTask(subtle::RunOrPostTaskPassKey,
const Location& from_here,
OnceClosure task);
// Submits a non-nestable task to delete the given object. Returns
// true if the object may be deleted at some point in the future,
// and false if the object definitely will not be deleted.
//
// By default, this leaks `object` if the deleter task doesn't run, e.g. if
// the underlying task queue is shut down first. Subclasses can override this
// behavior by specializing `DeleteOrReleaseSoonInternal()`.
template <class T>
bool DeleteSoon(const Location& from_here, const T* object) {
return DeleteOrReleaseSoonInternal(from_here, &DeleteHelper<T>::DoDelete,
object);
}
template <class T>
bool DeleteSoon(const Location& from_here, std::unique_ptr<T> object) {
return DeleteOrReleaseSoonInternal(
from_here, &DeleteUniquePtrHelper<T>::DoDelete, object.release());
}
// Submits a non-nestable task to release the given object.
//
// By default, this leaks `object` if the releaser task doesn't run, e.g. if
// the underlying task queue is shut down first. Subclasses can override this
// behavior by specializing `DeleteOrReleaseSoonInternal()`.
//
// ReleaseSoon makes sure that the object it the scoped_refptr points to gets
// properly released on the correct thread.
// We apply ReleaseSoon to the rvalue as the side-effects can be unclear to
// the caller if an lvalue is used. That being so, the scoped_refptr should
// always be std::move'd.
// Example use:
//
// scoped_refptr<T> foo_scoped_refptr;
// ...
// task_runner->ReleaseSoon(std::move(foo_scoped_refptr));
template <class T>
void ReleaseSoon(const Location& from_here, scoped_refptr<T>&& object) {
if (!object)
return;
DeleteOrReleaseSoonInternal(from_here, &ReleaseHelper<T>::DoRelease,
object.release());
}
// Returns true iff tasks posted to this TaskRunner are sequenced
// with this call.
//
// In particular:
// - Returns true if this is a SequencedTaskRunner to which the
// current task was posted.
// - Returns true if this is a SequencedTaskRunner bound to the
// same sequence as the SequencedTaskRunner to which the current
// task was posted.
// - Returns true if this is a SingleThreadTaskRunner bound to
// the current thread.
virtual bool RunsTasksInCurrentSequence() const = 0;
// Returns the default SequencedTaskRunner for the current task. It
// should only be called if HasCurrentDefault() returns true (see the comment
// there for the requirements).
//
// It is "default" in the sense that if the current sequence multiplexes
// multiple task queues (e.g. BrowserThread::UI), this will return the default
// task queue. A caller that wants a specific task queue should obtain it
// directly instead of going through this API.
//
// See
// https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md#Posting-to-the-Current-Virtual_Thread
// for details
[[nodiscard]] static const scoped_refptr<SequencedTaskRunner>&
GetCurrentDefault();
// Returns true if one of the following conditions is fulfilled:
// a) A SequencedTaskRunner has been assigned to the current thread by
// instantiating a SequencedTaskRunner::CurrentDefaultHandle.
// b) The current thread has a SingleThreadTaskRunner::CurrentDefaultHandle
// (which includes any thread that runs a MessagePump).
[[nodiscard]] static bool HasCurrentDefault();
class BASE_EXPORT CurrentDefaultHandle {
public:
// Sets the value returned by `SequencedTaskRunner::GetCurrentDefault()` to
// `task_runner` within its scope. `task_runner` must belong to the current
// sequence. There must not already be a current default
// `SequencedTaskRunner` on this thread.
explicit CurrentDefaultHandle(
scoped_refptr<SequencedTaskRunner> task_runner);
CurrentDefaultHandle(const CurrentDefaultHandle&) = delete;
CurrentDefaultHandle& operator=(const CurrentDefaultHandle&) = delete;
~CurrentDefaultHandle();
private:
friend class SequencedTaskRunner;
// Overriding an existing current default SingleThreadTaskRunner should only
// be needed under special circumstances. Require them to be enumerated as
// friends to require //base/OWNERS review. Use
// SingleThreadTaskRunner::CurrentHandleOverrideForTesting in unit tests to
// avoid the friend requirement.
friend class SingleThreadTaskRunner;
FRIEND_TEST_ALL_PREFIXES(SequencedTaskRunnerCurrentDefaultHandleTest,
OverrideWithNull);
FRIEND_TEST_ALL_PREFIXES(SequencedTaskRunnerCurrentDefaultHandleTest,
OverrideWithNonNull);
struct MayAlreadyExist {};
// Same as the public constructor, but there may already be a current
// default `SequencedTaskRunner` on this thread.
CurrentDefaultHandle(scoped_refptr<SequencedTaskRunner> task_runner,
MayAlreadyExist);
scoped_refptr<SequencedTaskRunner> task_runner_;
// RAW_PTR_EXCLUSION: Performance reasons (based on analysis of
// speedometer3).
RAW_PTR_EXCLUSION CurrentDefaultHandle* previous_handle_ = nullptr;
};
protected:
~SequencedTaskRunner() override = default;
virtual bool DeleteOrReleaseSoonInternal(const Location& from_here,
void (*deleter)(const void*),
const void* object);
};
// Sample usage with std::unique_ptr :
// std::unique_ptr<Foo, base::OnTaskRunnerDeleter> ptr(
// new Foo, base::OnTaskRunnerDeleter(my_task_runner));
//
// For RefCounted see base::RefCountedDeleteOnSequence.
struct BASE_EXPORT OnTaskRunnerDeleter {
explicit OnTaskRunnerDeleter(scoped_refptr<SequencedTaskRunner> task_runner);
~OnTaskRunnerDeleter();
OnTaskRunnerDeleter(OnTaskRunnerDeleter&&);
OnTaskRunnerDeleter& operator=(OnTaskRunnerDeleter&&);
// For compatibility with std:: deleters.
template <typename T>
void operator()(const T* ptr) {
if (ptr)
task_runner_->DeleteSoon(FROM_HERE, ptr);
}
scoped_refptr<SequencedTaskRunner> task_runner_;
};
} // namespace base
#endif // BASE_TASK_SEQUENCED_TASK_RUNNER_H_

Some files were not shown because too many files have changed in this diff Show More