blob: 7a5fe8c8b72a1158c9e0e45a4105d37caac289a4 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_SEQUENCE_BOUND_H_
#define BASE_THREADING_SEQUENCE_BOUND_H_
#include <new>
#include <tuple>
#include <type_traits>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/ptr_util.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequence_bound_internal.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace base {
// Performing blocking work on a different task runner is a common pattern for
// improving responsiveness of foreground task runners. `SequenceBound<T>`
// provides an abstraction for an owner object living on the owner sequence, to
// construct, call methods on, and destroy an object of type T that lives on a
// different sequence (the bound sequence).
//
// This makes it natural for code running on different sequences to be
// partitioned along class boundaries, e.g.:
//
// class Tab {
// private:
// void OnScroll() {
// // ...
// io_helper_.AsyncCall(&IOHelper::SaveScrollPosition);
// }
// SequenceBound<IOHelper> io_helper_{GetBackgroundTaskRunner()};
// };
//
// Note: `SequenceBound<T>` intentionally does not expose a raw pointer to the
// managed `T` to ensure its internal sequence-safety invariants are not
// violated. As a result, `AsyncCall()` cannot simply use `base::OnceCallback`
//
// SequenceBound also supports replies:
//
// class Database {
// public:
// int Query(int value) {
// return value * value;
// }
// };
//
// // SequenceBound itself is owned on `SequencedTaskRunnerHandle::Get()`.
// // The managed Database instance managed by it is constructed and owned on
// // `GetDBTaskRunner()`.
// SequenceBound<Database> db(GetDBTaskRunner());
//
// // `Database::Query()` runs on `GetDBTaskRunner()`, but
// // `reply_callback` will run on the owner task runner.
// auto reply_callback = [] (int result) {
// LOG(ERROR) << result; // Prints 25.
// };
// db.AsyncCall(&Database::Query).WithArgs(5)
// .Then(base::BindOnce(reply_callback));
//
// // When `db` goes out of scope, the Database instance will also be
// // destroyed via a task posted to `GetDBTaskRunner()`.
//
// TODO(dcheng): SequenceBound should only be constructed, used, and destroyed
// on a single sequence. This enforcement will gradually be enabled over time.
template <typename T>
class SequenceBound {
public:
// Note: on construction, SequenceBound binds to the current sequence. Any
// subsequent SequenceBound calls (including destruction) must run on that
// same sequence.
// Constructs a null SequenceBound with no managed `T`.
// TODO(dcheng): Add an `Emplace()` method to go with `Reset()`.
SequenceBound() = default;
// Schedules asynchronous construction of a new instance of `T` on
// `task_runner`.
//
// Once the SequenceBound constructor completes, the caller can immediately
// use `AsyncCall()`, et cetera, to schedule work after the construction of
// `T` on `task_runner`.
//
// Marked NO_SANITIZE because cfi doesn't like casting uninitialized memory to
// `T*`. However, this is safe here because:
//
// 1. The cast is well-defined (see https://eel.is/c++draft/basic.life#6) and
// 2. The resulting pointer is only ever dereferenced on `impl_task_runner_`.
// By the time SequenceBound's constructor returns, the task to construct
// `T` will already be posted; thus, subsequent dereference of `t_` on
// `impl_task_runner_` are safe.
template <typename... Args>
NO_SANITIZE("cfi-unrelated-cast")
SequenceBound(scoped_refptr<base::SequencedTaskRunner> task_runner,
Args&&... args)
: impl_task_runner_(std::move(task_runner)) {
// Allocate space for but do not construct an instance of `T`.
// AlignedAlloc() requires alignment be a multiple of sizeof(void*).
storage_ = AlignedAlloc(
sizeof(T), sizeof(void*) > alignof(T) ? sizeof(void*) : alignof(T));
t_ = reinterpret_cast<T*>(storage_);
// Ensure that `t_` will be initialized
impl_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ConstructOwnerRecord<Args...>, base::Unretained(t_),
std::forward<Args>(args)...));
}
// If non-null, destruction of the managed `T` is posted to
// `impl_task_runner_`.`
~SequenceBound() { Reset(); }
// Disallow copy or assignment. SequenceBound has single ownership of the
// managed `T`.
SequenceBound(const SequenceBound&) = delete;
SequenceBound& operator=(const SequenceBound&) = delete;
// Move construction and assignment.
SequenceBound(SequenceBound&& other) { MoveRecordFrom(other); }
SequenceBound& operator=(SequenceBound&& other) {
Reset();
MoveRecordFrom(other);
return *this;
}
// Move conversion helpers: allows upcasting from SequenceBound<Derived> to
// SequenceBound<Base>.
template <typename From>
SequenceBound(SequenceBound<From>&& other) {
MoveRecordFrom(other);
}
template <typename From>
SequenceBound<T>& operator=(SequenceBound<From>&& other) {
Reset();
MoveRecordFrom(other);
return *this;
}
// Invokes `method` of the managed `T` on `impl_task_runner_`. May only be
// used when `is_null()` is false.
//
// Basic usage:
//
// helper.AsyncCall(&IOHelper::DoWork);
//
// If `method` accepts arguments, use of `WithArgs()` to bind them is
// mandatory:
//
// helper.AsyncCall(&IOHelper::DoWorkWithArgs).WithArgs(args);
//
// Optionally, use `Then()` to chain to a callback on the owner sequence after
// `method` completes. If `method` returns a non-void type, the return value
// will be passed to the chained callback.
//
// helper.AsyncCall(&IOHelper::GetValue).Then(std::move(process_result));
//
// `WithArgs()` and `Then()` may also be combined:
//
// helper.AsyncCall(&IOHelper::GetValueWithArgs).WithArgs(args)
// .Then(std::move(process_result));
//
// but note that ordering is strict: `Then()` must always be last.
//
// Note: internally, this is implemented using a series of templated builders.
// Destruction of the builder may trigger task posting; as a result, using the
// builder as anything other than a temporary is not allowed.
//
// Similarly, triggering lifetime extension of the temporary (e.g. by binding
// to a const lvalue reference) is not allowed.
template <typename R, typename... Args>
auto AsyncCall(R (T::*method)(Args...),
const Location& location = Location::Current()) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return AsyncCallBuilder<R (T::*)(Args...)>(this, &location, method);
}
template <typename R, typename... Args>
auto AsyncCall(R (T::*method)(Args...) const,
const Location& location = Location::Current()) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return AsyncCallBuilder<R (T::*)(Args...) const>(this, &location, method);
}
// Post a call to `method` to `impl_task_runner_`.
// TODO(dcheng): Deprecate this in favor of `AsyncCall()`.
template <typename... MethodArgs, typename... Args>
void Post(const base::Location& from_here,
void (T::*method)(MethodArgs...),
Args&&... args) const {
DCHECK(t_);
impl_task_runner_->PostTask(from_here,
base::BindOnce(method, base::Unretained(t_),
std::forward<Args>(args)...));
}
// Posts `task` to `impl_task_runner_`, passing it a reference to the wrapped
// object. This allows arbitrary logic to be safely executed on the object's
// task runner. The object is guaranteed to remain alive for the duration of
// the task.
using ConstPostTaskCallback = base::OnceCallback<void(const T&)>;
void PostTaskWithThisObject(const base::Location& from_here,
ConstPostTaskCallback callback) const {
DCHECK(t_);
impl_task_runner_->PostTask(
from_here,
base::BindOnce([](ConstPostTaskCallback callback,
const T* t) { std::move(callback).Run(*t); },
std::move(callback), t_));
}
// Same as above, but for non-const operations. The callback takes a pointer
// to the wrapped object rather than a const ref.
using PostTaskCallback = base::OnceCallback<void(T*)>;
void PostTaskWithThisObject(const base::Location& from_here,
PostTaskCallback callback) const {
DCHECK(t_);
impl_task_runner_->PostTask(from_here,
base::BindOnce(std::move(callback), t_));
}
// TODO(liberato): Add PostOrCall(), to support cases where synchronous calls
// are okay if it's the same task runner.
// Resets `this` to null. If `this` is not currently null, posts destruction
// of the managed `T` to `impl_task_runner_`.
void Reset() {
if (is_null())
return;
impl_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DeleteOwnerRecord, base::Unretained(t_),
base::Unretained(storage_)));
impl_task_runner_ = nullptr;
t_ = nullptr;
storage_ = nullptr;
}
// Same as above, but allows the caller to provide a closure to be invoked
// immediately after destruction. The Closure is invoked on
// `impl_task_runner_`, iff the owned object was non-null.
//
// TODO(dcheng): Consider removing this; this appears to be used for test
// synchronization, but that could be achieved by posting
// `run_loop.QuitClosure()` to the destination sequence after calling
// `Reset()`.
void ResetWithCallbackAfterDestruction(base::OnceClosure callback) {
if (is_null())
return;
impl_task_runner_->PostTask(
FROM_HERE, base::BindOnce(
[](base::OnceClosure callback, T* t, void* storage) {
DeleteOwnerRecord(t, storage);
std::move(callback).Run();
},
std::move(callback), t_, storage_));
impl_task_runner_ = nullptr;
t_ = nullptr;
storage_ = nullptr;
}
// Return true if `this` is logically null; otherwise, returns false.
//
// A SequenceBound is logically null if there is no managed `T`; it is only
// valid to call `AsyncCall()` on a non-null SequenceBound.
//
// Note that the concept of 'logically null' here does not exactly match the
// lifetime of `T`, which lives on `impl_task_runner_`. In particular, when
// SequenceBound is first constructed, `is_null()` may return false, even
// though the lifetime of `T` may not have begun yet on `impl_task_runner_`.
// Similarly, after `SequenceBound::Reset()`, `is_null()` may return true,
// even though the lifetime of `T` may not have ended yet on
// `impl_task_runner_`.
bool is_null() const { return !t_; }
// True if `this` is not logically null. See `is_null()`.
explicit operator bool() const { return !is_null(); }
private:
// For move conversion.
template <typename U>
friend class SequenceBound;
// Support helpers for `AsyncCall()` implementation.
//
// Several implementation notes:
// 1. Tasks are posted via destroying the builder or an explicit call to
// `Then()`.
//
// 2. A builder may be consumed by:
//
// - calling `Then()`, which immediately posts the task chain
// - calling `WithArgs()`, which returns a new builder with the captured
// arguments
//
// Builders that are consumed have the internal `sequence_bound_` field
// nulled out; the hope is the compiler can see this and use it to
// eliminate dead branches (e.g. correctness checks that aren't needed
// since the code can be statically proved correct).
//
// 3. Builder methods are rvalue-qualified to try to enforce that the builder
// is only used as a temporary. Note that this only helps so much; nothing
// prevents a determined caller from using `std::move()` to force calls to
// a non-temporary instance.
//
// TODO(dcheng): It might also be possible to use Gmock-style matcher
// composition, e.g. something like:
//
// sb.AsyncCall(&Helper::DoWork, WithArgs(args),
// Then(std::move(process_result));
//
// In theory, this might allow the elimination of magic destructors and
// better static checking by the compiler.
template <typename MethodPtrType>
class AsyncCallBuilderBase {
protected:
AsyncCallBuilderBase(const SequenceBound* sequence_bound,
const Location* location,
MethodPtrType method)
: sequence_bound_(sequence_bound),
location_(location),
method_(method) {
// Common entry point for `AsyncCall()`, so check preconditions here.
DCHECK(sequence_bound_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_bound_->sequence_checker_);
DCHECK(sequence_bound_->t_);
}
AsyncCallBuilderBase(AsyncCallBuilderBase&&) = default;
AsyncCallBuilderBase& operator=(AsyncCallBuilderBase&&) = default;
// `sequence_bound_` is consumed and set to `nullptr` when `Then()` is
// invoked. This is used as a flag for two potential states
//
// - if a method returns void, invoking `Then()` is optional. The destructor
// will check if `sequence_bound_` is null; if it is, `Then()` was
// already invoked and the task chain has already been posted, so the
// destructor does not need to do anything. Otherwise, the destructor
// needs to post the task to make the async call. In theory, the compiler
// should be able to eliminate this branch based on the presence or
// absence of a call to `Then()`.
//
// - if a method returns a non-void type, `Then()` *must* be invoked. The
// destructor will `CHECK()` if `sequence_bound_` is non-null, since that
// indicates `Then()` was not invoked. Similarly, note this branch should
// be eliminated by the optimizer if the code is free of bugs. :)
const SequenceBound* sequence_bound_;
// Subtle: this typically points at a Location *temporary*. This is used to
// try to detect errors resulting from lifetime extension of the async call
// factory temporaries, since the factory destructors can perform work. If
// the lifetime of the factory is incorrectly extended, dereferencing
// `location_` will trigger a stack-use-after-scope when running with ASan.
const Location* const location_;
MethodPtrType method_;
};
template <typename MethodPtrType, typename ReturnType, typename... Args>
class AsyncCallBuilderImpl;
// Selected method has no arguments and returns void.
template <typename MethodPtrType>
class AsyncCallBuilderImpl<MethodPtrType, void, std::tuple<>>
: public AsyncCallBuilderBase<MethodPtrType> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallBuilderBase<MethodPtrType>::AsyncCallBuilderBase;
~AsyncCallBuilderImpl() {
if (this->sequence_bound_) {
this->sequence_bound_->impl_task_runner_->PostTask(
*this->location_,
BindOnce(this->method_, Unretained(this->sequence_bound_->t_)));
}
}
void Then(OnceClosure then_callback) && {
this->sequence_bound_->PostTaskAndThenHelper(
*this->location_,
BindOnce(this->method_, Unretained(this->sequence_bound_->t_)),
std::move(then_callback));
this->sequence_bound_ = nullptr;
}
private:
friend SequenceBound;
AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
};
// Selected method has no arguments and returns non-void.
template <typename MethodPtrType, typename ReturnType>
class AsyncCallBuilderImpl<MethodPtrType, ReturnType, std::tuple<>>
: public AsyncCallBuilderBase<MethodPtrType> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallBuilderBase<MethodPtrType>::AsyncCallBuilderBase;
~AsyncCallBuilderImpl() {
// Must use Then() since the method's return type is not void.
// Should be optimized out if the code is bug-free.
CHECK(!this->sequence_bound_)
<< "Then() not invoked for a method that returns a non-void type; "
<< "make sure to invoke Then() or use base::IgnoreResult()";
}
template <template <typename> class CallbackType,
typename ThenArg,
typename = EnableIfIsBaseCallback<CallbackType>>
void Then(CallbackType<void(ThenArg)> then_callback) && {
this->sequence_bound_->PostTaskAndThenHelper(
*this->location_,
BindOnce(this->method_, Unretained(this->sequence_bound_->t_)),
std::move(then_callback));
this->sequence_bound_ = nullptr;
}
private:
friend SequenceBound;
AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
};
// Selected method has arguments. Return type can be void or non-void.
template <typename MethodPtrType, typename ReturnType, typename... Args>
class AsyncCallBuilderImpl<MethodPtrType, ReturnType, std::tuple<Args...>>
: public AsyncCallBuilderBase<MethodPtrType> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallBuilderBase<MethodPtrType>::AsyncCallBuilderBase;
~AsyncCallBuilderImpl() {
// Must use WithArgs() since the method takes arguments.
// Should be optimized out if the code is bug-free.
CHECK(!this->sequence_bound_);
}
template <typename... BoundArgs>
auto WithArgs(BoundArgs&&... bound_args) {
const SequenceBound* const sequence_bound =
std::exchange(this->sequence_bound_, nullptr);
return AsyncCallWithBoundArgsBuilder<ReturnType>(
sequence_bound, this->location_,
BindOnce(this->method_, Unretained(sequence_bound->t_),
std::forward<BoundArgs>(bound_args)...));
}
private:
friend SequenceBound;
AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
};
template <typename MethodPtrType,
typename R = internal::ExtractMethodReturnType<MethodPtrType>,
typename ArgsTuple =
internal::ExtractMethodArgsTuple<MethodPtrType>>
using AsyncCallBuilder = AsyncCallBuilderImpl<MethodPtrType, R, ArgsTuple>;
// Support factories when arguments are bound using `WithArgs()`. These
// factories don't need to handle raw method pointers, since everything has
// already been packaged into a base::OnceCallback.
template <typename ReturnType>
class AsyncCallWithBoundArgsBuilderBase {
protected:
AsyncCallWithBoundArgsBuilderBase(const SequenceBound* sequence_bound,
const Location* location,
base::OnceCallback<ReturnType()> callback)
: sequence_bound_(sequence_bound),
location_(location),
callback_(std::move(callback)) {
DCHECK(sequence_bound_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_bound_->sequence_checker_);
DCHECK(sequence_bound_->t_);
}
// Subtle: the internal helpers rely on move elision. Preventing move
// elision (e.g. using `std::move()` when returning the temporary) will
// trigger a `CHECK()` since `sequence_bound_` is not reset to nullptr on
// move.
AsyncCallWithBoundArgsBuilderBase(
AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
AsyncCallWithBoundArgsBuilderBase& operator=(
AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
const SequenceBound* sequence_bound_;
const Location* const location_;
base::OnceCallback<ReturnType()> callback_;
};
// Note: this doesn't handle a void return type, which has an explicit
// specialization below.
template <typename ReturnType>
class AsyncCallWithBoundArgsBuilderDefault
: public AsyncCallWithBoundArgsBuilderBase<ReturnType> {
public:
~AsyncCallWithBoundArgsBuilderDefault() {
// Must use Then() since the method's return type is not void.
// Should be optimized out if the code is bug-free.
CHECK(!this->sequence_bound_);
}
template <template <typename> class CallbackType,
typename ThenArg,
typename = EnableIfIsBaseCallback<CallbackType>>
void Then(CallbackType<void(ThenArg)> then_callback) && {
this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
std::move(this->callback_),
std::move(then_callback));
this->sequence_bound_ = nullptr;
}
protected:
using AsyncCallWithBoundArgsBuilderBase<
ReturnType>::AsyncCallWithBoundArgsBuilderBase;
private:
friend SequenceBound;
AsyncCallWithBoundArgsBuilderDefault(
AsyncCallWithBoundArgsBuilderDefault&&) = default;
AsyncCallWithBoundArgsBuilderDefault& operator=(
AsyncCallWithBoundArgsBuilderDefault&&) = default;
};
class AsyncCallWithBoundArgsBuilderVoid
: public AsyncCallWithBoundArgsBuilderBase<void> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallWithBoundArgsBuilderBase<
void>::AsyncCallWithBoundArgsBuilderBase;
~AsyncCallWithBoundArgsBuilderVoid() {
if (this->sequence_bound_) {
this->sequence_bound_->impl_task_runner_->PostTask(
*this->location_, std::move(this->callback_));
}
}
void Then(OnceClosure then_callback) && {
this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
std::move(this->callback_),
std::move(then_callback));
this->sequence_bound_ = nullptr;
}
private:
friend SequenceBound;
AsyncCallWithBoundArgsBuilderVoid(AsyncCallWithBoundArgsBuilderVoid&&) =
default;
AsyncCallWithBoundArgsBuilderVoid& operator=(
AsyncCallWithBoundArgsBuilderVoid&&) = default;
};
template <typename ReturnType>
using AsyncCallWithBoundArgsBuilder = typename std::conditional<
std::is_void<ReturnType>::value,
AsyncCallWithBoundArgsBuilderVoid,
AsyncCallWithBoundArgsBuilderDefault<ReturnType>>::type;
void PostTaskAndThenHelper(const Location& location,
OnceCallback<void()> callback,
OnceClosure then_callback) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
impl_task_runner_->PostTaskAndReply(location, std::move(callback),
std::move(then_callback));
}
template <typename ReturnType,
template <typename>
class CallbackType,
typename ThenArg,
typename = EnableIfIsBaseCallback<CallbackType>>
void PostTaskAndThenHelper(const Location& location,
OnceCallback<ReturnType()> callback,
CallbackType<void(ThenArg)> then_callback) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
OnceCallback<void(ThenArg)>&& once_then_callback = std::move(then_callback);
impl_task_runner_->PostTaskAndReplyWithResult(
location, std::move(callback), std::move(once_then_callback));
}
// Helper to support move construction and move assignment.
//
// Marked NO_SANITIZE since:
// 1. SequenceBound can be moved before `t_` is constructed on
// `impl_task_runner_` but
// 2. Implicit conversions to non-virtual base classes are allowed before the
// lifetime of `t_` has started (see https://eel.is/c++draft/basic.life#6).
template <typename From>
void NO_SANITIZE("cfi-unrelated-cast") MoveRecordFrom(From&& other) {
// TODO(dcheng): Consider adding a static_assert to provide a friendlier
// error message.
impl_task_runner_ = std::move(other.impl_task_runner_);
// Subtle: this must not use static_cast<>, since the lifetime of the
// managed `T` may not have begun yet. However, the standard explicitly
// still allows implicit conversion to a non-virtual base class.
t_ = std::exchange(other.t_, nullptr);
storage_ = std::exchange(other.storage_, nullptr);
}
// Pointer to the managed `T`. This field is only read and written on
// the sequence associated with `sequence_checker_`.
T* t_ = nullptr;
// Storage originally allocated by `AlignedAlloc()`. Maintained separately
// from `t_` since the original, unadjusted pointer needs to be passed to
// `AlignedFree()`.
void* storage_ = nullptr;
SEQUENCE_CHECKER(sequence_checker_);
// Task runner which manages `t_`. `t_` is constructed, destroyed, and
// dereferenced only on this task runner.
scoped_refptr<base::SequencedTaskRunner> impl_task_runner_;
// Helpers for constructing and destroying `T` on `impl_task_runner_`.
template <typename... Args>
static void ConstructOwnerRecord(T* t, std::decay_t<Args>&&... args) {
new (t) T(std::move(args)...);
}
static void DeleteOwnerRecord(T* t, void* storage) {
t->~T();
AlignedFree(storage);
}
};
} // namespace base
#endif // BASE_THREADING_SEQUENCE_BOUND_H_