naiveproxy/src/base/threading/sequence_bound.h
2022-05-29 18:11:59 +08:00

750 lines
29 KiB
C++

// 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/check.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace base {
namespace internal {
struct DefaultCrossThreadBindTraits {
template <typename Signature>
using CrossThreadTask = OnceCallback<Signature>;
template <typename Functor, typename... Args>
static inline auto BindOnce(Functor&& functor, Args&&... args) {
return base::BindOnce(std::forward<Functor>(functor),
std::forward<Args>(args)...);
}
template <typename T>
static inline auto Unretained(T ptr) {
return base::Unretained(ptr);
}
template <typename Signature>
static inline bool PostTask(SequencedTaskRunner& task_runner,
const Location& location,
CrossThreadTask<Signature>&& task) {
return task_runner.PostTask(location, std::move(task));
}
static inline bool PostTaskAndReply(SequencedTaskRunner& task_runner,
const Location& location,
OnceClosure&& task,
OnceClosure&& reply) {
return task_runner.PostTaskAndReply(location, std::move(task),
std::move(reply));
}
template <typename TaskReturnType, typename ReplyArgType>
static inline bool PostTaskAndReplyWithResult(
SequencedTaskRunner& task_runner,
const Location& location,
OnceCallback<TaskReturnType()>&& task,
OnceCallback<void(ReplyArgType)>&& reply) {
return task_runner.PostTaskAndReplyWithResult(location, std::move(task),
std::move(reply));
}
// Accept RepeatingCallback here since it's convertible to a OnceCallback.
template <template <typename> class CallbackType>
using EnableIfIsCrossThreadTask = EnableIfIsBaseCallback<CallbackType>;
};
} // namespace internal
// 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()`.
//
// Sequence safety:
//
// Const-qualified methods may be used concurrently from multiple sequences,
// e.g. `AsyncCall()` or `is_null()`. Calls that are forwarded to the
// managed `T` will be posted to the bound sequence and executed serially
// there.
//
// Mutable methods (e.g. `Reset()`, destruction, or move assignment) require
// external synchronization if used concurrently with any other methods,
// including const-qualified methods.
template <typename T,
class CrossThreadBindTraits = internal::DefaultCrossThreadBindTraits>
class SequenceBound {
public:
template <typename Signature>
using CrossThreadTask =
typename CrossThreadBindTraits::template CrossThreadTask<Signature>;
// 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")
explicit SequenceBound(scoped_refptr<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
CrossThreadBindTraits::PostTask(
*impl_task_runner_, FROM_HERE,
CrossThreadBindTraits::BindOnce(&ConstructOwnerRecord<Args...>,
CrossThreadBindTraits::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>
// NOLINTNEXTLINE(google-explicit-constructor): Intentionally implicit.
SequenceBound(SequenceBound<From, CrossThreadBindTraits>&& other) {
MoveRecordFrom(other);
}
template <typename From>
SequenceBound& operator=(SequenceBound<From, CrossThreadBindTraits>&& 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 `WithArgs()` to bind them:
//
// helper.AsyncCall(&IOHelper::DoWorkWithArgs)
// .WithArgs(args);
//
// Use `Then()` to run a callback on the owner sequence after `method`
// completes:
//
// helper.AsyncCall(&IOHelper::GetValue)
// .Then(std::move(process_result_callback));
//
// If a method returns a non-void type, use of `Then()` is required, and the
// method's return value will be passed to the `Then()` callback. To ignore
// the method's return value instead, wrap `method` in `base::IgnoreResult()`:
//
// // Calling `GetPrefs` to force-initialize prefs.
// helper.AsyncCall(base::IgnoreResult(&IOHelper::GetPrefs));
//
// `WithArgs()` and `Then()` may also be combined:
//
// // Ordering is important: `Then()` must come last.
// helper.AsyncCall(&IOHelper::GetValueWithArgs)
// .WithArgs(args)
// .Then(std::move(process_result_callback));
//
// Note: internally, `AsyncCall()` is implemented using a series of helper
// classes that build the callback chain and post it on destruction. Capturing
// the return value and passing it elsewhere or triggering lifetime extension
// (e.g. by binding the return value to a reference) are both unsupported.
template <typename R, typename... Args>
auto AsyncCall(R (T::*method)(Args...),
const Location& location = Location::Current()) const {
return AsyncCallBuilder<R (T::*)(Args...), R, std::tuple<Args...>>(
this, &location, method);
}
template <typename R, typename... Args>
auto AsyncCall(R (T::*method)(Args...) const,
const Location& location = Location::Current()) const {
return AsyncCallBuilder<R (T::*)(Args...) const, R, std::tuple<Args...>>(
this, &location, method);
}
template <typename R, typename... Args>
auto AsyncCall(internal::IgnoreResultHelper<R (T::*)(Args...) const> method,
const Location& location = Location::Current()) const {
return AsyncCallBuilder<
internal::IgnoreResultHelper<R (T::*)(Args...) const>, void,
std::tuple<Args...>>(this, &location, method);
}
template <typename R, typename... Args>
auto AsyncCall(internal::IgnoreResultHelper<R (T::*)(Args...)> method,
const Location& location = Location::Current()) const {
return AsyncCallBuilder<internal::IgnoreResultHelper<R (T::*)(Args...)>,
void, std::tuple<Args...>>(this, &location, method);
}
// 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.
// TODO(crbug.com/1182140): Consider checking whether the task runner can run
// tasks in current sequence, and using "plain" binds and task posting (here
// and other places that `CrossThreadBindTraits::PostTask`).
using ConstPostTaskCallback = CrossThreadTask<void(const T&)>;
void PostTaskWithThisObject(
ConstPostTaskCallback callback,
const Location& location = Location::Current()) const {
DCHECK(!is_null());
// Even though the lifetime of the object pointed to by `t_` may not have
// begun yet, the storage has been allocated. Per [basic.life/6] and
// [basic.life/7], "Indirection through such a pointer is permitted but the
// resulting lvalue may only be used in limited ways, as described below."
CrossThreadBindTraits::PostTask(
*impl_task_runner_, location,
CrossThreadBindTraits::BindOnce(std::move(callback), std::cref(*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 = CrossThreadTask<void(T*)>;
void PostTaskWithThisObject(
PostTaskCallback callback,
const Location& location = Location::Current()) const {
DCHECK(!is_null());
CrossThreadBindTraits::PostTask(
*impl_task_runner_, location,
CrossThreadBindTraits::BindOnce(std::move(callback),
CrossThreadBindTraits::Unretained(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;
CrossThreadBindTraits::PostTask(
*impl_task_runner_, FROM_HERE,
CrossThreadBindTraits::BindOnce(
&DeleteOwnerRecord, CrossThreadBindTraits::Unretained(t_),
CrossThreadBindTraits::Unretained(storage_)));
impl_task_runner_ = nullptr;
t_ = nullptr;
storage_ = nullptr;
}
// Resets `this` to null. If `this` is not currently null, posts destruction
// of the managed `T` to `impl_task_runner_`. Blocks until the destructor has
// run.
void SynchronouslyResetForTest() {
if (is_null())
return;
RunLoop run_loop;
CrossThreadBindTraits::PostTask(
*impl_task_runner_, FROM_HERE,
CrossThreadBindTraits::BindOnce(
[](T* t, void* storage) { DeleteOwnerRecord(t, storage); }, t_,
storage_)
.Then(run_loop.QuitClosure()));
run_loop.Run();
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, class Binder>
friend class SequenceBound;
template <template <typename> class CallbackType>
using EnableIfIsCrossThreadTask =
typename CrossThreadBindTraits::template EnableIfIsCrossThreadTask<
CallbackType>;
// 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 MethodRef>
class AsyncCallBuilderBase {
protected:
AsyncCallBuilderBase(const SequenceBound* sequence_bound,
const Location* location,
MethodRef method)
: sequence_bound_(sequence_bound),
location_(location),
method_(method) {
// Common entry point for `AsyncCall()`, so check preconditions here.
DCHECK(sequence_bound_);
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. :)
raw_ptr<const SequenceBound<T, CrossThreadBindTraits>> 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 raw_ptr<const Location> location_;
MethodRef method_;
};
template <typename MethodRef, typename ReturnType, typename ArgsTuple>
class AsyncCallBuilderImpl;
// Selected method has no arguments and returns void.
template <typename MethodRef>
class AsyncCallBuilderImpl<MethodRef, void, std::tuple<>>
: public AsyncCallBuilderBase<MethodRef> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
~AsyncCallBuilderImpl() {
if (this->sequence_bound_) {
CrossThreadBindTraits::PostTask(
*this->sequence_bound_->impl_task_runner_, *this->location_,
CrossThreadBindTraits::BindOnce(
this->method_,
CrossThreadBindTraits::Unretained(this->sequence_bound_->t_)));
}
}
void Then(OnceClosure then_callback) && {
this->sequence_bound_->PostTaskAndThenHelper(
*this->location_,
CrossThreadBindTraits::BindOnce(
this->method_,
CrossThreadBindTraits::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 MethodRef, typename ReturnType>
class AsyncCallBuilderImpl<MethodRef, ReturnType, std::tuple<>>
: public AsyncCallBuilderBase<MethodRef> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallBuilderBase<MethodRef>::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 = EnableIfIsCrossThreadTask<CallbackType>>
void Then(CallbackType<void(ThenArg)> then_callback) && {
this->sequence_bound_->PostTaskAndThenHelper(
*this->location_,
CrossThreadBindTraits::BindOnce(
this->method_,
CrossThreadBindTraits::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 MethodRef, typename ReturnType, typename... Args>
class AsyncCallBuilderImpl<MethodRef, ReturnType, std::tuple<Args...>>
: public AsyncCallBuilderBase<MethodRef> {
public:
// Note: despite being here, this is actually still protected, since it is
// protected on the base class.
using AsyncCallBuilderBase<MethodRef>::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_,
CrossThreadBindTraits::BindOnce(
this->method_,
CrossThreadBindTraits::Unretained(sequence_bound->t_),
std::forward<BoundArgs>(bound_args)...));
}
private:
friend SequenceBound;
AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
};
// `MethodRef` is either a member function pointer type or a member function
// pointer type wrapped with `internal::IgnoreResultHelper`.
// `R` is the return type of `MethodRef`. This is always `void` if
// `MethodRef` is an `internal::IgnoreResultHelper` wrapper.
// `ArgsTuple` is a `std::tuple` with template type arguments corresponding to
// the types of the method's parameters.
template <typename MethodRef, typename R, typename ArgsTuple>
using AsyncCallBuilder = AsyncCallBuilderImpl<MethodRef, 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,
CrossThreadTask<ReturnType()> callback)
: sequence_bound_(sequence_bound),
location_(location),
callback_(std::move(callback)) {
DCHECK(sequence_bound_);
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;
raw_ptr<const SequenceBound<T, CrossThreadBindTraits>> sequence_bound_;
const raw_ptr<const Location> location_;
CrossThreadTask<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 = EnableIfIsCrossThreadTask<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_) {
CrossThreadBindTraits::PostTask(
*this->sequence_bound_->impl_task_runner_, *this->location_,
std::move(this->callback_));
}
}
void Then(CrossThreadTask<void()> 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,
CrossThreadTask<void()> callback,
CrossThreadTask<void()> then_callback) const {
CrossThreadBindTraits::PostTaskAndReply(*impl_task_runner_, location,
std::move(callback),
std::move(then_callback));
}
template <typename ReturnType,
template <typename>
class CallbackType,
typename ThenArg,
typename = EnableIfIsCrossThreadTask<CallbackType>>
void PostTaskAndThenHelper(const Location& location,
CrossThreadTask<ReturnType()> callback,
CallbackType<void(ThenArg)> then_callback) const {
CrossThreadTask<void(ThenArg)>&& once_then_callback =
std::move(then_callback);
CrossThreadBindTraits::PostTaskAndReplyWithResult(
*impl_task_runner_, 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`.
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;
// Task runner which manages `t_`. `t_` is constructed, destroyed, and
// dereferenced only on this task runner.
scoped_refptr<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_