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