// 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 #include #include #include #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` // 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 io_helper_{GetBackgroundTaskRunner()}; // }; // // Note: `SequenceBound` 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 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 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 NO_SANITIZE("cfi-unrelated-cast") SequenceBound(scoped_refptr 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(storage_); // Ensure that `t_` will be initialized impl_task_runner_->PostTask( FROM_HERE, base::BindOnce(&ConstructOwnerRecord, base::Unretained(t_), std::forward(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 to // SequenceBound. template SequenceBound(SequenceBound&& other) { MoveRecordFrom(other); } template SequenceBound& operator=(SequenceBound&& 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 auto AsyncCall(R (T::*method)(Args...), const Location& location = Location::Current()) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return AsyncCallBuilder(this, &location, method); } template auto AsyncCall(R (T::*method)(Args...) const, const Location& location = Location::Current()) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return AsyncCallBuilder(this, &location, method); } // Post a call to `method` to `impl_task_runner_`. // TODO(dcheng): Deprecate this in favor of `AsyncCall()`. template 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)...)); } // 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 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 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 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 class AsyncCallBuilderBase { protected: AsyncCallBuilderBase(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_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. :) 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 class AsyncCallBuilderImpl; // Selected method has no arguments and returns void. template class AsyncCallBuilderImpl> : public AsyncCallBuilderBase { public: // Note: despite being here, this is actually still protected, since it is // protected on the base class. using AsyncCallBuilderBase::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 class AsyncCallBuilderImpl> : public AsyncCallBuilderBase { public: // Note: despite being here, this is actually still protected, since it is // protected on the base class. using AsyncCallBuilderBase::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