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