mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2026-06-04 21:36:29 +03:00
Import chromium-132.0.6834.79
This commit is contained in:
7
src/base/task/DIR_METADATA
Normal file
7
src/base/task/DIR_METADATA
Normal 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
8
src/base/task/OWNERS
Normal 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
16
src/base/task/README.md
Normal 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)
|
||||
128
src/base/task/bind_post_task.h
Normal file
128
src/base/task/bind_post_task.h
Normal 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_
|
||||
106
src/base/task/bind_post_task_internal.h
Normal file
106
src/base/task/bind_post_task_internal.h
Normal 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_
|
||||
55
src/base/task/bind_post_task_nocompile.nc
Normal file
55
src/base/task/bind_post_task_nocompile.nc
Normal 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
|
||||
188
src/base/task/cancelable_task_tracker.cc
Normal file
188
src/base/task/cancelable_task_tracker.cc
Normal 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
|
||||
164
src/base/task/cancelable_task_tracker.h
Normal file
164
src/base/task/cancelable_task_tracker.h
Normal 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_
|
||||
7
src/base/task/common/DIR_METADATA
Normal file
7
src/base/task/common/DIR_METADATA
Normal file
@@ -0,0 +1,7 @@
|
||||
monorail: {
|
||||
component: "Internals>TaskScheduling"
|
||||
}
|
||||
team_email: "scheduler-dev@chromium.org"
|
||||
buganizer_public: {
|
||||
component_id: 1456866
|
||||
}
|
||||
2
src/base/task/common/OWNERS
Normal file
2
src/base/task/common/OWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
file://base/task/sequence_manager/OWNERS
|
||||
file://base/task/thread_pool/OWNERS
|
||||
153
src/base/task/common/checked_lock.h
Normal file
153
src/base/task/common/checked_lock.h
Normal 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_
|
||||
195
src/base/task/common/checked_lock_impl.cc
Normal file
195
src/base/task/common/checked_lock_impl.cc
Normal 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
|
||||
62
src/base/task/common/checked_lock_impl.h
Normal file
62
src/base/task/common/checked_lock_impl.h
Normal 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_
|
||||
41
src/base/task/common/lazy_now.cc
Normal file
41
src/base/task/common/lazy_now.cc
Normal 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
|
||||
45
src/base/task/common/lazy_now.h
Normal file
45
src/base/task/common/lazy_now.h
Normal 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_
|
||||
108
src/base/task/common/operations_controller.cc
Normal file
108
src/base/task/common/operations_controller.cc
Normal 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
|
||||
156
src/base/task/common/operations_controller.h
Normal file
156
src/base/task/common/operations_controller.h
Normal 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_
|
||||
101
src/base/task/common/scoped_defer_task_posting.cc
Normal file
101
src/base/task/common/scoped_defer_task_posting.cc
Normal 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
|
||||
82
src/base/task/common/scoped_defer_task_posting.h
Normal file
82
src/base/task/common/scoped_defer_task_posting.h
Normal 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_
|
||||
433
src/base/task/common/task_annotator.cc
Normal file
433
src/base/task/common/task_annotator.cc
Normal 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(¤t_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(¤t_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(¤t_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(¤t_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_(¤t_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_(¤t_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
|
||||
214
src/base/task/common/task_annotator.h
Normal file
214
src/base/task/common/task_annotator.h
Normal 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_
|
||||
270
src/base/task/current_thread.cc
Normal file
270
src/base/task/current_thread.cc
Normal 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
|
||||
350
src/base/task/current_thread.h
Normal file
350
src/base/task/current_thread.h
Normal 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_
|
||||
39
src/base/task/default_delayed_task_handle_delegate.cc
Normal file
39
src/base/task/default_delayed_task_handle_delegate.cc
Normal 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
|
||||
40
src/base/task/default_delayed_task_handle_delegate.h
Normal file
40
src/base/task/default_delayed_task_handle_delegate.h
Normal 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_
|
||||
147
src/base/task/deferred_sequenced_task_runner.cc
Normal file
147
src/base/task/deferred_sequenced_task_runner.cc
Normal 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
|
||||
103
src/base/task/deferred_sequenced_task_runner.h
Normal file
103
src/base/task/deferred_sequenced_task_runner.h
Normal 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_
|
||||
43
src/base/task/delay_policy.h
Normal file
43
src/base/task/delay_policy.h
Normal 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_
|
||||
48
src/base/task/delayed_task_handle.cc
Normal file
48
src/base/task/delayed_task_handle.cc
Normal 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
|
||||
56
src/base/task/delayed_task_handle.h
Normal file
56
src/base/task/delayed_task_handle.h
Normal 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_
|
||||
127
src/base/task/lazy_thread_pool_task_runner.cc
Normal file
127
src/base/task/lazy_thread_pool_task_runner.cc
Normal 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
|
||||
215
src/base/task/lazy_thread_pool_task_runner.h
Normal file
215
src/base/task/lazy_thread_pool_task_runner.h
Normal 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
189
src/base/task/post_job.cc
Normal 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
213
src/base/task/post_job.h
Normal 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 doesn’t 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_
|
||||
38
src/base/task/post_task_and_reply_with_result_internal.h
Normal file
38
src/base/task/post_task_and_reply_with_result_internal.h
Normal 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_
|
||||
38
src/base/task/scoped_set_task_priority_for_current_thread.cc
Normal file
38
src/base/task/scoped_set_task_priority_for_current_thread.cc
Normal 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
|
||||
40
src/base/task/scoped_set_task_priority_for_current_thread.h
Normal file
40
src/base/task/scoped_set_task_priority_for_current_thread.h
Normal 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_
|
||||
6
src/base/task/sequence_manager/DIR_METADATA
Normal file
6
src/base/task/sequence_manager/DIR_METADATA
Normal file
@@ -0,0 +1,6 @@
|
||||
monorail: {
|
||||
component: "Internals>SequenceManager"
|
||||
}
|
||||
buganizer_public: {
|
||||
component_id: 1456159
|
||||
}
|
||||
6
src/base/task/sequence_manager/OWNERS
Normal file
6
src/base/task/sequence_manager/OWNERS
Normal file
@@ -0,0 +1,6 @@
|
||||
altimin@chromium.org
|
||||
carlscab@google.com
|
||||
etiennep@chromium.org
|
||||
fdoray@chromium.org
|
||||
shaseley@chromium.org
|
||||
skyostil@chromium.org
|
||||
68
src/base/task/sequence_manager/README.md
Normal file
68
src/base/task/sequence_manager/README.md
Normal 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.
|
||||
89
src/base/task/sequence_manager/associated_thread_id.cc
Normal file
89
src/base/task/sequence_manager/associated_thread_id.cc
Normal 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
|
||||
111
src/base/task/sequence_manager/associated_thread_id.h
Normal file
111
src/base/task/sequence_manager/associated_thread_id.h
Normal 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_
|
||||
209
src/base/task/sequence_manager/atomic_flag_set.cc
Normal file
209
src/base/task/sequence_manager/atomic_flag_set.cc
Normal 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
|
||||
139
src/base/task/sequence_manager/atomic_flag_set.h
Normal file
139
src/base/task/sequence_manager/atomic_flag_set.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
61
src/base/task/sequence_manager/enqueue_order.h
Normal file
61
src/base/task/sequence_manager/enqueue_order.h
Normal 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_
|
||||
18
src/base/task/sequence_manager/enqueue_order_generator.cc
Normal file
18
src/base/task/sequence_manager/enqueue_order_generator.cc
Normal 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
|
||||
42
src/base/task/sequence_manager/enqueue_order_generator.h
Normal file
42
src/base/task/sequence_manager/enqueue_order_generator.h
Normal 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_
|
||||
45
src/base/task/sequence_manager/fence.cc
Normal file
45
src/base/task/sequence_manager/fence.cc
Normal 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
|
||||
67
src/base/task/sequence_manager/fence.h
Normal file
67
src/base/task/sequence_manager/fence.h
Normal 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_
|
||||
389
src/base/task/sequence_manager/lazily_deallocated_deque.h
Normal file
389
src/base/task/sequence_manager/lazily_deallocated_deque.h
Normal 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_
|
||||
192
src/base/task/sequence_manager/sequence_manager.cc
Normal file
192
src/base/task/sequence_manager/sequence_manager.cc
Normal 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
|
||||
384
src/base/task/sequence_manager/sequence_manager.h
Normal file
384
src/base/task/sequence_manager/sequence_manager.h
Normal 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_
|
||||
1261
src/base/task/sequence_manager/sequence_manager_impl.cc
Normal file
1261
src/base/task/sequence_manager/sequence_manager_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
522
src/base/task/sequence_manager/sequence_manager_impl.h
Normal file
522
src/base/task/sequence_manager/sequence_manager_impl.h
Normal 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_
|
||||
27
src/base/task/sequence_manager/sequenced_task_source.cc
Normal file
27
src/base/task/sequence_manager/sequenced_task_source.cc
Normal 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
|
||||
107
src/base/task/sequence_manager/sequenced_task_source.h
Normal file
107
src/base/task/sequence_manager/sequenced_task_source.h
Normal 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_
|
||||
89
src/base/task/sequence_manager/task_order.cc
Normal file
89
src/base/task/sequence_manager/task_order.cc
Normal 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
|
||||
91
src/base/task/sequence_manager/task_order.h
Normal file
91
src/base/task/sequence_manager/task_order.h
Normal 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_
|
||||
105
src/base/task/sequence_manager/task_queue.cc
Normal file
105
src/base/task/sequence_manager/task_queue.cc
Normal 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
|
||||
446
src/base/task/sequence_manager/task_queue.h
Normal file
446
src/base/task/sequence_manager/task_queue.h
Normal 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_
|
||||
1698
src/base/task/sequence_manager/task_queue_impl.cc
Normal file
1698
src/base/task/sequence_manager/task_queue_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
631
src/base/task/sequence_manager/task_queue_impl.h
Normal file
631
src/base/task/sequence_manager/task_queue_impl.h
Normal 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_
|
||||
287
src/base/task/sequence_manager/task_queue_selector.cc
Normal file
287
src/base/task/sequence_manager/task_queue_selector.cc
Normal 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
|
||||
266
src/base/task/sequence_manager/task_queue_selector.h
Normal file
266
src/base/task/sequence_manager/task_queue_selector.h
Normal 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_
|
||||
13
src/base/task/sequence_manager/task_time_observer.cc
Normal file
13
src/base/task/sequence_manager/task_time_observer.cc
Normal 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
|
||||
33
src/base/task/sequence_manager/task_time_observer.h
Normal file
33
src/base/task/sequence_manager/task_time_observer.h
Normal 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_
|
||||
150
src/base/task/sequence_manager/tasks.cc
Normal file
150
src/base/task/sequence_manager/tasks.cc
Normal 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
|
||||
180
src/base/task/sequence_manager/tasks.h
Normal file
180
src/base/task/sequence_manager/tasks.h
Normal 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_
|
||||
47
src/base/task/sequence_manager/test/fake_task.cc
Normal file
47
src/base/task/sequence_manager/test/fake_task.cc
Normal 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
|
||||
33
src/base/task/sequence_manager/test/fake_task.h
Normal file
33
src/base/task/sequence_manager/test/fake_task.h
Normal 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_
|
||||
36
src/base/task/sequence_manager/test/mock_time_domain.cc
Normal file
36
src/base/task/sequence_manager/test/mock_time_domain.cc
Normal 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
|
||||
42
src/base/task/sequence_manager/test/mock_time_domain.h
Normal file
42
src/base/task/sequence_manager/test/mock_time_domain.h
Normal 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_
|
||||
@@ -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
|
||||
87
src/base/task/sequence_manager/test/mock_time_message_pump.h
Normal file
87
src/base/task/sequence_manager/test/mock_time_message_pump.h
Normal 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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
728
src/base/task/sequence_manager/thread_controller.cc
Normal file
728
src/base/task/sequence_manager/thread_controller.cc
Normal 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
|
||||
488
src/base/task/sequence_manager/thread_controller.h
Normal file
488
src/base/task/sequence_manager/thread_controller.h
Normal 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_
|
||||
376
src/base/task/sequence_manager/thread_controller_impl.cc
Normal file
376
src/base/task/sequence_manager/thread_controller_impl.cc
Normal 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
|
||||
132
src/base/task/sequence_manager/thread_controller_impl.h
Normal file
132
src/base/task/sequence_manager/thread_controller_impl.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
32
src/base/task/sequence_manager/time_domain.cc
Normal file
32
src/base/task/sequence_manager/time_domain.cc
Normal 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
|
||||
70
src/base/task/sequence_manager/time_domain.h
Normal file
70
src/base/task/sequence_manager/time_domain.h
Normal 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_
|
||||
191
src/base/task/sequence_manager/wake_up_queue.cc
Normal file
191
src/base/task/sequence_manager/wake_up_queue.cc
Normal 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
|
||||
158
src/base/task/sequence_manager/wake_up_queue.h
Normal file
158
src/base/task/sequence_manager/wake_up_queue.h
Normal 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_
|
||||
78
src/base/task/sequence_manager/work_deduplicator.cc
Normal file
78
src/base/task/sequence_manager/work_deduplicator.cc
Normal 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
|
||||
145
src/base/task/sequence_manager/work_deduplicator.h
Normal file
145
src/base/task/sequence_manager/work_deduplicator.h
Normal 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_
|
||||
331
src/base/task/sequence_manager/work_queue.cc
Normal file
331
src/base/task/sequence_manager/work_queue.cc
Normal 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
|
||||
188
src/base/task/sequence_manager/work_queue.h
Normal file
188
src/base/task/sequence_manager/work_queue.h
Normal 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_
|
||||
232
src/base/task/sequence_manager/work_queue_sets.cc
Normal file
232
src/base/task/sequence_manager/work_queue_sets.cc
Normal 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
|
||||
161
src/base/task/sequence_manager/work_queue_sets.h
Normal file
161
src/base/task/sequence_manager/work_queue_sets.h
Normal 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_
|
||||
142
src/base/task/sequence_manager/work_tracker.cc
Normal file
142
src/base/task/sequence_manager/work_tracker.cc
Normal 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
|
||||
134
src/base/task/sequence_manager/work_tracker.h
Normal file
134
src/base/task/sequence_manager/work_tracker.h
Normal 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_
|
||||
141
src/base/task/sequenced_task_runner.cc
Normal file
141
src/base/task/sequenced_task_runner.cc
Normal 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
|
||||
415
src/base/task/sequenced_task_runner.h
Normal file
415
src/base/task/sequenced_task_runner.h
Normal 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
Reference in New Issue
Block a user