xref: /aosp_15_r20/external/cronet/base/threading/sequence_bound.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_THREADING_SEQUENCE_BOUND_H_
6 #define BASE_THREADING_SEQUENCE_BOUND_H_
7 
8 #include <concepts>
9 #include <new>
10 #include <tuple>
11 #include <type_traits>
12 #include <utility>
13 
14 #include "base/check.h"
15 #include "base/location.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/memory/scoped_refptr.h"
18 #include "base/run_loop.h"
19 #include "base/sequence_checker.h"
20 #include "base/task/sequenced_task_runner.h"
21 #include "base/threading/sequence_bound_internal.h"
22 
23 namespace base {
24 
25 // Performing blocking work on a different task runner is a common pattern for
26 // improving responsiveness of foreground task runners. `SequenceBound<T>`
27 // provides an abstraction for an owner object living on the owner sequence, to
28 // construct, call methods on, and destroy an object of type T that lives on a
29 // different sequence (the bound sequence).
30 //
31 // This makes it natural for code running on different sequences to be
32 // partitioned along class boundaries, e.g.:
33 //
34 // class Tab {
35 //  private:
36 //   void OnScroll() {
37 //     // ...
38 //     io_helper_.AsyncCall(&IOHelper::SaveScrollPosition);
39 //   }
40 //   base::SequenceBound<IOHelper> io_helper_{GetBackgroundTaskRunner()};
41 // };
42 //
43 // Note: `SequenceBound<T>` intentionally does not expose a raw pointer to the
44 // managed `T` to ensure its internal sequence-safety invariants are not
45 // violated. As a result, `AsyncCall()` cannot simply use `base::OnceCallback`
46 //
47 // SequenceBound also supports replies:
48 //
49 //   class Database {
50 //    public:
51 //     int Query(int value) {
52 //       return value * value;
53 //     }
54 //   };
55 //
56 //   // SequenceBound itself is owned on
57 //   // `SequencedTaskRunner::GetCurrentDefault()`. The managed Database
58 //   // instance managed by it is constructed and owned on `GetDBTaskRunner()`.
59 //   base::SequenceBound<Database> db(GetDBTaskRunner());
60 //
61 //   // `Database::Query()` runs on `GetDBTaskRunner()`, but
62 //   // `reply_callback` will run on the owner task runner.
63 //   auto reply_callback = [] (int result) {
64 //     LOG(ERROR) << result;  // Prints 25.
65 //   };
66 //   db.AsyncCall(&Database::Query).WithArgs(5)
67 //     .Then(base::BindOnce(reply_callback));
68 //
69 //   // When `db` goes out of scope, the Database instance will also be
70 //   // destroyed via a task posted to `GetDBTaskRunner()`.
71 //
72 // Sequence safety:
73 //
74 // Const-qualified methods may be used concurrently from multiple sequences,
75 // e.g. `AsyncCall()` or `is_null()`. Calls that are forwarded to the
76 // managed `T` will be posted to the bound sequence and executed serially
77 // there.
78 //
79 // Mutable methods (e.g. `Reset()`, destruction, or move assignment) require
80 // external synchronization if used concurrently with any other methods,
81 // including const-qualified methods.
82 //
83 // Advanced usage:
84 //
85 // Using `SequenceBound<std::unique_ptr<T>>` allows transferring ownership of an
86 // already-constructed `T` to `SequenceBound`. This can be helpful for more
87 // complex situations, where `T` needs to be constructed on a specific sequence
88 // that is different from where `T` will ultimately live.
89 //
90 // Construction (via the constructor or emplace) takes a `std::unique_ptr<T>`
91 // instead of forwarding the arguments to `T`'s constructor:
92 //
93 //   std::unique_ptr<Database> db_impl = MakeDatabaseOnMainThread();
94 //   base::SequenceBound<std::unique_ptr<Database>> db(GetDbTaskRunner(),
95 //                                                     std::move(db_impl));
96 //
97 // All other usage (e.g. `AsyncCall()`, `Reset()`) functions identically to a
98 // regular `SequenceBound<T>`:
99 //
100 //   // No need to dereference the `std::unique_ptr` explicitly:
101 //   db.AsyncCall(&Database::Query).WithArgs(5).Then(base::BindOnce(...));
102 template <typename T,
103           typename CrossThreadTraits =
104               sequence_bound_internal::CrossThreadTraits>
105 class SequenceBound {
106  private:
107   using Storage = sequence_bound_internal::Storage<T, CrossThreadTraits>;
108   using UnwrappedT = typename Storage::element_type;
109 
110  public:
111   template <typename Signature>
112   using CrossThreadTask =
113       typename CrossThreadTraits::template CrossThreadTask<Signature>;
114 
115   // Note: on construction, SequenceBound binds to the current sequence. Any
116   // subsequent SequenceBound calls (including destruction) must run on that
117   // same sequence.
118 
119   // Constructs a null SequenceBound with no managed `T`.
120   SequenceBound() = default;
121 
122   // Constructs a SequenceBound that manages a new instance of `T` on
123   // `task_runner`. `T` will be constructed on `task_runner`.
124   //
125   // Once this constructor returns, it is safe to immediately use `AsyncCall()`,
126   // et cetera; these calls will be sequenced after the construction of the
127   // managed `T`.
128   template <typename... Args>
SequenceBound(scoped_refptr<SequencedTaskRunner> task_runner,Args &&...args)129   explicit SequenceBound(scoped_refptr<SequencedTaskRunner> task_runner,
130                          Args&&... args)
131       : impl_task_runner_(std::move(task_runner)) {
132     storage_.Construct(*impl_task_runner_, std::forward<Args>(args)...);
133   }
134 
135   // If non-null, the managed `T` will be destroyed on `impl_task_runner_`.`
~SequenceBound()136   ~SequenceBound() { Reset(); }
137 
138   // Disallow copy or assignment. SequenceBound has single ownership of the
139   // managed `T`.
140   SequenceBound(const SequenceBound&) = delete;
141   SequenceBound& operator=(const SequenceBound&) = delete;
142 
143   // Move construction and assignment.
SequenceBound(SequenceBound && other)144   SequenceBound(SequenceBound&& other) { MoveRecordFrom(other); }
145 
146   SequenceBound& operator=(SequenceBound&& other) {
147     Reset();
148     MoveRecordFrom(other);
149     return *this;
150   }
151 
152   // Move conversion helpers: allows upcasting from SequenceBound<Derived> to
153   // SequenceBound<Base>.
154   template <typename U>
155   // NOLINTNEXTLINE(google-explicit-constructor): Intentionally implicit.
SequenceBound(SequenceBound<U,CrossThreadTraits> && other)156   SequenceBound(SequenceBound<U, CrossThreadTraits>&& other) {
157     // TODO(https://crbug.com/1382549): static_assert that U* is convertible to
158     // T*.
159     MoveRecordFrom(other);
160   }
161 
162   template <typename U>
163   SequenceBound& operator=(SequenceBound<U, CrossThreadTraits>&& other) {
164     // TODO(https://crbug.com/1382549): static_assert that U* is convertible to
165     // T*.
166     Reset();
167     MoveRecordFrom(other);
168     return *this;
169   }
170 
171   // Constructs a new managed instance of `T` on `task_runner`. If `this` is
172   // already managing another instance of `T`, that pre-existing instance will
173   // first be destroyed by calling `Reset()`.
174   //
175   // Once `emplace()` returns, it is safe to immediately use `AsyncCall()`,
176   // et cetera; these calls will be sequenced after the construction of the
177   // managed `T`.
178   template <typename... Args>
emplace(scoped_refptr<SequencedTaskRunner> task_runner,Args &&...args)179   SequenceBound& emplace(scoped_refptr<SequencedTaskRunner> task_runner,
180                          Args&&... args) {
181     Reset();
182     impl_task_runner_ = std::move(task_runner);
183     storage_.Construct(*impl_task_runner_, std::forward<Args>(args)...);
184     return *this;
185   }
186 
187   // Invokes `method` of the managed `T` on `impl_task_runner_`. May only be
188   // used when `is_null()` is false.
189   //
190   // Basic usage:
191   //
192   //   helper.AsyncCall(&IOHelper::DoWork);
193   //
194   // If `method` accepts arguments, use `WithArgs()` to bind them:
195   //
196   //   helper.AsyncCall(&IOHelper::DoWorkWithArgs)
197   //       .WithArgs(args);
198   //
199   // Use `Then()` to run a callback on the owner sequence after `method`
200   // completes:
201   //
202   //   helper.AsyncCall(&IOHelper::GetValue)
203   //       .Then(std::move(process_result_callback));
204   //
205   // If a method returns a non-void type, use of `Then()` is required, and the
206   // method's return value will be passed to the `Then()` callback. To ignore
207   // the method's return value instead, wrap `method` in `base::IgnoreResult()`:
208   //
209   //   // Calling `GetPrefs` to force-initialize prefs.
210   //   helper.AsyncCall(base::IgnoreResult(&IOHelper::GetPrefs));
211   //
212   // `WithArgs()` and `Then()` may also be combined:
213   //
214   //   // Ordering is important: `Then()` must come last.
215   //   helper.AsyncCall(&IOHelper::GetValueWithArgs)
216   //       .WithArgs(args)
217   //       .Then(std::move(process_result_callback));
218   //
219   // Note: internally, `AsyncCall()` is implemented using a series of helper
220   // classes that build the callback chain and post it on destruction. Capturing
221   // the return value and passing it elsewhere or triggering lifetime extension
222   // (e.g. by binding the return value to a reference) are both unsupported.
223   template <typename R, typename C, typename... Args>
requires(std::derived_from<UnwrappedT,C>)224     requires(std::derived_from<UnwrappedT, C>)
225   auto AsyncCall(R (C::*method)(Args...),
226                  const Location& location = Location::Current()) const {
227     return AsyncCallBuilder<R (C::*)(Args...), R, std::tuple<Args...>>(
228         this, &location, method);
229   }
230 
231   template <typename R, typename C, typename... Args>
requires(std::derived_from<UnwrappedT,C>)232     requires(std::derived_from<UnwrappedT, C>)
233   auto AsyncCall(R (C::*method)(Args...) const,
234                  const Location& location = Location::Current()) const {
235     return AsyncCallBuilder<R (C::*)(Args...) const, R, std::tuple<Args...>>(
236         this, &location, method);
237   }
238 
239   template <typename R, typename C, typename... Args>
requires(std::derived_from<UnwrappedT,C>)240     requires(std::derived_from<UnwrappedT, C>)
241   auto AsyncCall(internal::IgnoreResultHelper<R (C::*)(Args...) const> method,
242                  const Location& location = Location::Current()) const {
243     return AsyncCallBuilder<
244         internal::IgnoreResultHelper<R (C::*)(Args...) const>, void,
245         std::tuple<Args...>>(this, &location, method);
246   }
247 
248   template <typename R, typename C, typename... Args>
requires(std::derived_from<UnwrappedT,C>)249     requires(std::derived_from<UnwrappedT, C>)
250   auto AsyncCall(internal::IgnoreResultHelper<R (C::*)(Args...)> method,
251                  const Location& location = Location::Current()) const {
252     return AsyncCallBuilder<internal::IgnoreResultHelper<R (C::*)(Args...)>,
253                             void, std::tuple<Args...>>(this, &location, method);
254   }
255 
256   // Posts `task` to `impl_task_runner_`, passing it a reference to the wrapped
257   // object. This allows arbitrary logic to be safely executed on the object's
258   // task runner. The object is guaranteed to remain alive for the duration of
259   // the task.
260   // TODO(crbug.com/1182140): Consider checking whether the task runner can run
261   // tasks in current sequence, and using "plain" binds and task posting (here
262   // and other places that `CrossThreadTraits::PostTask`).
263   using ConstPostTaskCallback = CrossThreadTask<void(const UnwrappedT&)>;
264   void PostTaskWithThisObject(
265       ConstPostTaskCallback callback,
266       const Location& location = Location::Current()) const {
267     DCHECK(!is_null());
268     // Even though the lifetime of the object managed by `storage_` may not
269     // have begun yet, the storage has been allocated. Per [basic.life/6] and
270     // [basic.life/7], "Indirection through such a pointer is permitted but the
271     // resulting lvalue may only be used in limited ways, as described below."
272     CrossThreadTraits::PostTask(
273         *impl_task_runner_, location,
274         CrossThreadTraits::BindOnce(std::move(callback),
275                                     storage_.GetRefForBind()));
276   }
277 
278   // Same as above, but for non-const operations. The callback takes a pointer
279   // to the wrapped object rather than a const ref.
280   using PostTaskCallback = CrossThreadTask<void(UnwrappedT*)>;
281   void PostTaskWithThisObject(
282       PostTaskCallback callback,
283       const Location& location = Location::Current()) const {
284     DCHECK(!is_null());
285     CrossThreadTraits::PostTask(
286         *impl_task_runner_, location,
287         CrossThreadTraits::BindOnce(std::move(callback),
288                                     storage_.GetPtrForBind()));
289   }
290 
FlushPostedTasksForTesting()291   void FlushPostedTasksForTesting() const {
292     DCHECK(!is_null());
293     RunLoop run_loop;
294     CrossThreadTraits::PostTask(*impl_task_runner_, FROM_HERE,
295                                 run_loop.QuitClosure());
296     run_loop.Run();
297   }
298 
299   // TODO(liberato): Add PostOrCall(), to support cases where synchronous calls
300   // are okay if it's the same task runner.
301 
302   // Resets `this` to null. If `this` is not currently null, posts destruction
303   // of the managed `T` to `impl_task_runner_`.
Reset()304   void Reset() {
305     if (is_null())
306       return;
307 
308     storage_.Destruct(*impl_task_runner_);
309     impl_task_runner_ = nullptr;
310   }
311 
312   // Resets `this` to null. If `this` is not currently null, posts destruction
313   // of the managed `T` to `impl_task_runner_`. Blocks until the destructor has
314   // run.
SynchronouslyResetForTest()315   void SynchronouslyResetForTest() {
316     if (is_null())
317       return;
318 
319     scoped_refptr<SequencedTaskRunner> task_runner = impl_task_runner_;
320     Reset();
321     // `Reset()` posts a task to destroy the managed `T`; synchronously wait for
322     // that posted task to complete.
323     RunLoop run_loop;
324     CrossThreadTraits::PostTask(*task_runner, FROM_HERE,
325                                 run_loop.QuitClosure());
326     run_loop.Run();
327   }
328 
329   // Return true if `this` is logically null; otherwise, returns false.
330   //
331   // A SequenceBound is logically null if there is no managed `T`; it is only
332   // valid to call `AsyncCall()` on a non-null SequenceBound.
333   //
334   // Note that the concept of 'logically null' here does not exactly match the
335   // lifetime of `T`, which lives on `impl_task_runner_`. In particular, when
336   // SequenceBound is first constructed, `is_null()` may return false, even
337   // though the lifetime of `T` may not have begun yet on `impl_task_runner_`.
338   // Similarly, after `SequenceBound::Reset()`, `is_null()` may return true,
339   // even though the lifetime of `T` may not have ended yet on
340   // `impl_task_runner_`.
is_null()341   bool is_null() const { return storage_.is_null(); }
342 
343   // True if `this` is not logically null. See `is_null()`.
344   explicit operator bool() const { return !is_null(); }
345 
346  private:
347   // For move conversion.
348   template <typename U, typename V>
349   friend class SequenceBound;
350 
351   template <template <typename> class CallbackType>
352   static constexpr bool IsCrossThreadTask =
353       CrossThreadTraits::template IsCrossThreadTask<CallbackType>;
354 
355   // Support helpers for `AsyncCall()` implementation.
356   //
357   // Several implementation notes:
358   // 1. Tasks are posted via destroying the builder or an explicit call to
359   //    `Then()`.
360   //
361   // 2. A builder may be consumed by:
362   //
363   //    - calling `Then()`, which immediately posts the task chain
364   //    - calling `WithArgs()`, which returns a new builder with the captured
365   //      arguments
366   //
367   //    Builders that are consumed have the internal `sequence_bound_` field
368   //    nulled out; the hope is the compiler can see this and use it to
369   //    eliminate dead branches (e.g. correctness checks that aren't needed
370   //    since the code can be statically proved correct).
371   //
372   // 3. Builder methods are rvalue-qualified to try to enforce that the builder
373   //    is only used as a temporary. Note that this only helps so much; nothing
374   //    prevents a determined caller from using `std::move()` to force calls to
375   //    a non-temporary instance.
376   //
377   // TODO(dcheng): It might also be possible to use Gmock-style matcher
378   // composition, e.g. something like:
379   //
380   //   sb.AsyncCall(&Helper::DoWork, WithArgs(args),
381   //                Then(std::move(process_result));
382   //
383   // In theory, this might allow the elimination of magic destructors and
384   // better static checking by the compiler.
385   template <typename MethodRef>
386   class AsyncCallBuilderBase {
387    protected:
AsyncCallBuilderBase(const SequenceBound * sequence_bound,const Location * location,MethodRef method)388     AsyncCallBuilderBase(const SequenceBound* sequence_bound,
389                          const Location* location,
390                          MethodRef method)
391         : sequence_bound_(sequence_bound),
392           location_(location),
393           method_(method) {
394       // Common entry point for `AsyncCall()`, so check preconditions here.
395       DCHECK(sequence_bound_);
396       DCHECK(!sequence_bound_->storage_.is_null());
397     }
398 
399     AsyncCallBuilderBase(AsyncCallBuilderBase&&) = default;
400     AsyncCallBuilderBase& operator=(AsyncCallBuilderBase&&) = default;
401 
402     // `sequence_bound_` is consumed and set to `nullptr` when `Then()` is
403     // invoked. This is used as a flag for two potential states
404     //
405     // - if a method returns void, invoking `Then()` is optional. The destructor
406     //   will check if `sequence_bound_` is null; if it is, `Then()` was
407     //   already invoked and the task chain has already been posted, so the
408     //   destructor does not need to do anything. Otherwise, the destructor
409     //   needs to post the task to make the async call. In theory, the compiler
410     //   should be able to eliminate this branch based on the presence or
411     //   absence of a call to `Then()`.
412     //
413     // - if a method returns a non-void type, `Then()` *must* be invoked. The
414     //   destructor will `CHECK()` if `sequence_bound_` is non-null, since that
415     //   indicates `Then()` was not invoked. Similarly, note this branch should
416     //   be eliminated by the optimizer if the code is free of bugs. :)
417     raw_ptr<const SequenceBound<T, CrossThreadTraits>, DanglingUntriaged>
418         sequence_bound_;
419     // Subtle: this typically points at a Location *temporary*. This is used to
420     // try to detect errors resulting from lifetime extension of the async call
421     // factory temporaries, since the factory destructors can perform work. If
422     // the lifetime of the factory is incorrectly extended, dereferencing
423     // `location_` will trigger a stack-use-after-scope when running with ASan.
424     const raw_ptr<const Location> location_;
425     MethodRef method_;
426   };
427 
428   template <typename MethodRef, typename ReturnType, typename ArgsTuple>
429   class AsyncCallBuilderImpl;
430 
431   // Selected method has no arguments and returns void.
432   template <typename MethodRef>
433   class AsyncCallBuilderImpl<MethodRef, void, std::tuple<>>
434       : public AsyncCallBuilderBase<MethodRef> {
435    public:
436     // Note: despite being here, this is actually still protected, since it is
437     // protected on the base class.
438     using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
439 
~AsyncCallBuilderImpl()440     ~AsyncCallBuilderImpl() {
441       if (this->sequence_bound_) {
442         CrossThreadTraits::PostTask(
443             *this->sequence_bound_->impl_task_runner_, *this->location_,
444             CrossThreadTraits::BindOnce(
445                 this->method_,
446                 this->sequence_bound_->storage_.GetPtrForBind()));
447       }
448     }
449 
Then(CrossThreadTask<void ()> then_callback)450     void Then(CrossThreadTask<void()> then_callback) && {
451       this->sequence_bound_->PostTaskAndThenHelper(
452           *this->location_,
453           CrossThreadTraits::BindOnce(
454               this->method_, this->sequence_bound_->storage_.GetPtrForBind()),
455           std::move(then_callback));
456       this->sequence_bound_ = nullptr;
457     }
458 
459    private:
460     friend SequenceBound;
461 
462     AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
463     AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
464   };
465 
466   // Selected method has no arguments and returns non-void.
467   template <typename MethodRef, typename ReturnType>
468   class AsyncCallBuilderImpl<MethodRef, ReturnType, std::tuple<>>
469       : public AsyncCallBuilderBase<MethodRef> {
470    public:
471     // Note: despite being here, this is actually still protected, since it is
472     // protected on the base class.
473     using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
474 
~AsyncCallBuilderImpl()475     ~AsyncCallBuilderImpl() {
476       // Must use Then() since the method's return type is not void.
477       // Should be optimized out if the code is bug-free.
478       CHECK(!this->sequence_bound_)
479           << "Then() not invoked for a method that returns a non-void type; "
480           << "make sure to invoke Then() or use base::IgnoreResult()";
481     }
482 
483     template <template <typename> class CallbackType, typename ThenArg>
requires(IsCrossThreadTask<CallbackType>)484       requires(IsCrossThreadTask<CallbackType>)
485     void Then(CallbackType<void(ThenArg)> then_callback) && {
486       this->sequence_bound_->PostTaskAndThenHelper(
487           *this->location_,
488           CrossThreadTraits::BindOnce(
489               this->method_, this->sequence_bound_->storage_.GetPtrForBind()),
490           std::move(then_callback));
491       this->sequence_bound_ = nullptr;
492     }
493 
494    private:
495     friend SequenceBound;
496 
497     AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
498     AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
499   };
500 
501   // Selected method has arguments. Return type can be void or non-void.
502   template <typename MethodRef, typename ReturnType, typename... Args>
503   class AsyncCallBuilderImpl<MethodRef, ReturnType, std::tuple<Args...>>
504       : public AsyncCallBuilderBase<MethodRef> {
505    public:
506     // Note: despite being here, this is actually still protected, since it is
507     // protected on the base class.
508     using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
509 
~AsyncCallBuilderImpl()510     ~AsyncCallBuilderImpl() {
511       // Must use WithArgs() since the method takes arguments.
512       // Should be optimized out if the code is bug-free.
513       CHECK(!this->sequence_bound_);
514     }
515 
516     template <typename... BoundArgs>
WithArgs(BoundArgs &&...bound_args)517     auto WithArgs(BoundArgs&&... bound_args) {
518       const SequenceBound* const sequence_bound =
519           std::exchange(this->sequence_bound_, nullptr);
520       return AsyncCallWithBoundArgsBuilder<ReturnType>(
521           sequence_bound, this->location_,
522           CrossThreadTraits::BindOnce(this->method_,
523                                       sequence_bound->storage_.GetPtrForBind(),
524                                       std::forward<BoundArgs>(bound_args)...));
525     }
526 
527    private:
528     friend SequenceBound;
529 
530     AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
531     AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
532   };
533 
534   // `MethodRef` is either a member function pointer type or a member function
535   //     pointer type wrapped with `internal::IgnoreResultHelper`.
536   // `R` is the return type of `MethodRef`. This is always `void` if
537   //     `MethodRef` is an `internal::IgnoreResultHelper` wrapper.
538   // `ArgsTuple` is a `std::tuple` with template type arguments corresponding to
539   //     the types of the method's parameters.
540   template <typename MethodRef, typename R, typename ArgsTuple>
541   using AsyncCallBuilder = AsyncCallBuilderImpl<MethodRef, R, ArgsTuple>;
542 
543   // Support factories when arguments are bound using `WithArgs()`. These
544   // factories don't need to handle raw method pointers, since everything has
545   // already been packaged into a base::OnceCallback.
546   template <typename ReturnType>
547   class AsyncCallWithBoundArgsBuilderBase {
548    protected:
AsyncCallWithBoundArgsBuilderBase(const SequenceBound * sequence_bound,const Location * location,CrossThreadTask<ReturnType ()> callback)549     AsyncCallWithBoundArgsBuilderBase(const SequenceBound* sequence_bound,
550                                       const Location* location,
551                                       CrossThreadTask<ReturnType()> callback)
552         : sequence_bound_(sequence_bound),
553           location_(location),
554           callback_(std::move(callback)) {
555       DCHECK(sequence_bound_);
556       DCHECK(!sequence_bound_->storage_.is_null());
557     }
558 
559     // Subtle: the internal helpers rely on move elision. Preventing move
560     // elision (e.g. using `std::move()` when returning the temporary) will
561     // trigger a `CHECK()` since `sequence_bound_` is not reset to nullptr on
562     // move.
563     AsyncCallWithBoundArgsBuilderBase(
564         AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
565     AsyncCallWithBoundArgsBuilderBase& operator=(
566         AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
567 
568     raw_ptr<const SequenceBound<T, CrossThreadTraits>> sequence_bound_;
569     const raw_ptr<const Location> location_;
570     CrossThreadTask<ReturnType()> callback_;
571   };
572 
573   // Note: this doesn't handle a void return type, which has an explicit
574   // specialization below.
575   template <typename ReturnType>
576   class AsyncCallWithBoundArgsBuilderDefault
577       : public AsyncCallWithBoundArgsBuilderBase<ReturnType> {
578    public:
~AsyncCallWithBoundArgsBuilderDefault()579     ~AsyncCallWithBoundArgsBuilderDefault() {
580       // Must use Then() since the method's return type is not void.
581       // Should be optimized out if the code is bug-free.
582       CHECK(!this->sequence_bound_);
583     }
584 
585     template <template <typename> class CallbackType, typename ThenArg>
requires(IsCrossThreadTask<CallbackType>)586       requires(IsCrossThreadTask<CallbackType>)
587     void Then(CallbackType<void(ThenArg)> then_callback) && {
588       this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
589                                                    std::move(this->callback_),
590                                                    std::move(then_callback));
591       this->sequence_bound_ = nullptr;
592     }
593 
594    protected:
595     using AsyncCallWithBoundArgsBuilderBase<
596         ReturnType>::AsyncCallWithBoundArgsBuilderBase;
597 
598    private:
599     friend SequenceBound;
600 
601     AsyncCallWithBoundArgsBuilderDefault(
602         AsyncCallWithBoundArgsBuilderDefault&&) = default;
603     AsyncCallWithBoundArgsBuilderDefault& operator=(
604         AsyncCallWithBoundArgsBuilderDefault&&) = default;
605   };
606 
607   class AsyncCallWithBoundArgsBuilderVoid
608       : public AsyncCallWithBoundArgsBuilderBase<void> {
609    public:
610     // Note: despite being here, this is actually still protected, since it is
611     // protected on the base class.
612     using AsyncCallWithBoundArgsBuilderBase<
613         void>::AsyncCallWithBoundArgsBuilderBase;
614 
~AsyncCallWithBoundArgsBuilderVoid()615     ~AsyncCallWithBoundArgsBuilderVoid() {
616       if (this->sequence_bound_) {
617         CrossThreadTraits::PostTask(*this->sequence_bound_->impl_task_runner_,
618                                     *this->location_,
619                                     std::move(this->callback_));
620       }
621     }
622 
Then(CrossThreadTask<void ()> then_callback)623     void Then(CrossThreadTask<void()> then_callback) && {
624       this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
625                                                    std::move(this->callback_),
626                                                    std::move(then_callback));
627       this->sequence_bound_ = nullptr;
628     }
629 
630    private:
631     friend SequenceBound;
632 
633     AsyncCallWithBoundArgsBuilderVoid(AsyncCallWithBoundArgsBuilderVoid&&) =
634         default;
635     AsyncCallWithBoundArgsBuilderVoid& operator=(
636         AsyncCallWithBoundArgsBuilderVoid&&) = default;
637   };
638 
639   template <typename ReturnType>
640   using AsyncCallWithBoundArgsBuilder = typename std::conditional<
641       std::is_void_v<ReturnType>,
642       AsyncCallWithBoundArgsBuilderVoid,
643       AsyncCallWithBoundArgsBuilderDefault<ReturnType>>::type;
644 
PostTaskAndThenHelper(const Location & location,CrossThreadTask<void ()> callback,CrossThreadTask<void ()> then_callback)645   void PostTaskAndThenHelper(const Location& location,
646                              CrossThreadTask<void()> callback,
647                              CrossThreadTask<void()> then_callback) const {
648     CrossThreadTraits::PostTaskAndReply(*impl_task_runner_, location,
649                                         std::move(callback),
650                                         std::move(then_callback));
651   }
652 
653   template <typename ReturnType,
654             template <typename>
655             class CallbackType,
656             typename ThenArg>
requires(IsCrossThreadTask<CallbackType>)657     requires(IsCrossThreadTask<CallbackType>)
658   void PostTaskAndThenHelper(const Location& location,
659                              CrossThreadTask<ReturnType()> callback,
660                              CallbackType<void(ThenArg)> then_callback) const {
661     CrossThreadTask<void(ThenArg)>&& once_then_callback =
662         std::move(then_callback);
663     CrossThreadTraits::PostTaskAndReplyWithResult(
664         *impl_task_runner_, location, std::move(callback),
665         std::move(once_then_callback));
666   }
667 
668   // Helper to support move construction and move assignment.
669   //
670   // TODO(https://crbug.com/1382549): Constrain this so converting between
671   // std::unique_ptr<T> and T are explicitly forbidden (rather than simply
672   // failing to build in spectacular ways).
673   template <typename From>
MoveRecordFrom(From && other)674   void MoveRecordFrom(From&& other) {
675     impl_task_runner_ = std::move(other.impl_task_runner_);
676 
677     storage_.TakeFrom(std::move(other.storage_));
678   }
679 
680   Storage storage_;
681 
682   // Task runner which manages `storage_`. An object owned by `storage_` (if
683   // any) will be constructed, destroyed, and otherwise used only on this task
684   // runner.
685   scoped_refptr<SequencedTaskRunner> impl_task_runner_;
686 };
687 
688 }  // namespace base
689 
690 #endif  // BASE_THREADING_SEQUENCE_BOUND_H_
691