// 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_THREADING_SEQUENCE_BOUND_H_ #define BASE_THREADING_SEQUENCE_BOUND_H_ #include #include #include #include #include #include "base/check.h" #include "base/location.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/sequence_bound_internal.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); // } // base::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 // // `SequencedTaskRunner::GetCurrentDefault()`. The managed Database // // instance managed by it is constructed and owned on `GetDBTaskRunner()`. // base::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()`. // // 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. // // Advanced usage: // // Using `SequenceBound>` allows transferring ownership of an // already-constructed `T` to `SequenceBound`. This can be helpful for more // complex situations, where `T` needs to be constructed on a specific sequence // that is different from where `T` will ultimately live. // // Construction (via the constructor or emplace) takes a `std::unique_ptr` // instead of forwarding the arguments to `T`'s constructor: // // std::unique_ptr db_impl = MakeDatabaseOnMainThread(); // base::SequenceBound> db(GetDbTaskRunner(), // std::move(db_impl)); // // All other usage (e.g. `AsyncCall()`, `Reset()`) functions identically to a // regular `SequenceBound`: // // // No need to dereference the `std::unique_ptr` explicitly: // db.AsyncCall(&Database::Query).WithArgs(5).Then(base::BindOnce(...)); template class SequenceBound { private: using Storage = sequence_bound_internal::Storage; using UnwrappedT = typename Storage::element_type; public: template using CrossThreadTask = typename CrossThreadTraits::template CrossThreadTask; // 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`. SequenceBound() = default; // Constructs a SequenceBound that manages a new instance of `T` on // `task_runner`. `T` will be constructed on `task_runner`. // // Once this constructor returns, it is safe to immediately use `AsyncCall()`, // et cetera; these calls will be sequenced after the construction of the // managed `T`. template explicit SequenceBound(scoped_refptr task_runner, Args&&... args) : impl_task_runner_(std::move(task_runner)) { storage_.Construct(*impl_task_runner_, std::forward(args)...); } // If non-null, the managed `T` will be destroyed on `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 // NOLINTNEXTLINE(google-explicit-constructor): Intentionally implicit. SequenceBound(SequenceBound&& other) { // TODO(https://crbug.com/1382549): static_assert that U* is convertible to // T*. MoveRecordFrom(other); } template SequenceBound& operator=(SequenceBound&& other) { // TODO(https://crbug.com/1382549): static_assert that U* is convertible to // T*. Reset(); MoveRecordFrom(other); return *this; } // Constructs a new managed instance of `T` on `task_runner`. If `this` is // already managing another instance of `T`, that pre-existing instance will // first be destroyed by calling `Reset()`. // // Once `emplace()` returns, it is safe to immediately use `AsyncCall()`, // et cetera; these calls will be sequenced after the construction of the // managed `T`. template SequenceBound& emplace(scoped_refptr task_runner, Args&&... args) { Reset(); impl_task_runner_ = std::move(task_runner); storage_.Construct(*impl_task_runner_, std::forward(args)...); 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 requires(std::derived_from) auto AsyncCall(R (C::*method)(Args...), const Location& location = Location::Current()) const { return AsyncCallBuilder>( this, &location, method); } template requires(std::derived_from) auto AsyncCall(R (C::*method)(Args...) const, const Location& location = Location::Current()) const { return AsyncCallBuilder>( this, &location, method); } template requires(std::derived_from) auto AsyncCall(internal::IgnoreResultHelper method, const Location& location = Location::Current()) const { return AsyncCallBuilder< internal::IgnoreResultHelper, void, std::tuple>(this, &location, method); } template requires(std::derived_from) auto AsyncCall(internal::IgnoreResultHelper method, const Location& location = Location::Current()) const { return AsyncCallBuilder, void, std::tuple>(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 `CrossThreadTraits::PostTask`). using ConstPostTaskCallback = CrossThreadTask; void PostTaskWithThisObject( ConstPostTaskCallback callback, const Location& location = Location::Current()) const { DCHECK(!is_null()); // Even though the lifetime of the object managed by `storage_` 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." CrossThreadTraits::PostTask( *impl_task_runner_, location, CrossThreadTraits::BindOnce(std::move(callback), storage_.GetRefForBind())); } // 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 PostTaskWithThisObject( PostTaskCallback callback, const Location& location = Location::Current()) const { DCHECK(!is_null()); CrossThreadTraits::PostTask( *impl_task_runner_, location, CrossThreadTraits::BindOnce(std::move(callback), storage_.GetPtrForBind())); } void FlushPostedTasksForTesting() const { DCHECK(!is_null()); RunLoop run_loop; CrossThreadTraits::PostTask(*impl_task_runner_, FROM_HERE, run_loop.QuitClosure()); run_loop.Run(); } // 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; storage_.Destruct(*impl_task_runner_); impl_task_runner_ = 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; scoped_refptr task_runner = impl_task_runner_; Reset(); // `Reset()` posts a task to destroy the managed `T`; synchronously wait for // that posted task to complete. RunLoop run_loop; CrossThreadTraits::PostTask(*task_runner, FROM_HERE, run_loop.QuitClosure()); run_loop.Run(); } // 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 storage_.is_null(); } // 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; template