ex/run_async.hpp

85.2% Lines (98/115) 92.4% Functions (2833/3065) 100.0% Branches (9/9)
ex/run_async.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_RUN_ASYNC_HPP
11 #define BOOST_CAPY_RUN_ASYNC_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/detail/run.hpp>
15 #include <boost/capy/detail/run_callbacks.hpp>
16 #include <boost/capy/concept/executor.hpp>
17 #include <boost/capy/concept/io_runnable.hpp>
18 #include <boost/capy/ex/execution_context.hpp>
19 #include <boost/capy/ex/frame_allocator.hpp>
20 #include <boost/capy/ex/io_env.hpp>
21 #include <boost/capy/ex/recycling_memory_resource.hpp>
22 #include <boost/capy/ex/work_guard.hpp>
23
24 #include <coroutine>
25 #include <cstring>
26 #include <memory_resource>
27 #include <new>
28 #include <stop_token>
29 #include <type_traits>
30
31 namespace boost {
32 namespace capy {
33 namespace detail {
34
35 /// Function pointer type for type-erased frame deallocation.
36 using dealloc_fn = void(*)(void*, std::size_t);
37
38 /// Type-erased deallocator implementation for trampoline frames.
39 template<class Alloc>
40 void dealloc_impl(void* raw, std::size_t total)
41 {
42 static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
43 auto* a = std::launder(reinterpret_cast<Alloc*>(
44 static_cast<char*>(raw) + total - sizeof(Alloc)));
45 Alloc ba(std::move(*a));
46 a->~Alloc();
47 ba.deallocate(static_cast<std::byte*>(raw), total);
48 }
49
50 /// Awaiter to access the promise from within the coroutine.
51 template<class Promise>
52 struct get_promise_awaiter
53 {
54 Promise* p_ = nullptr;
55
56 2013 bool await_ready() const noexcept { return false; }
57
58 2013 bool await_suspend(std::coroutine_handle<Promise> h) noexcept
59 {
60 2013 p_ = &h.promise();
61 2013 return false;
62 }
63
64 2013 Promise& await_resume() const noexcept
65 {
66 2013 return *p_;
67 }
68 };
69
70 /** Internal run_async_trampoline coroutine for run_async.
71
72 The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
73 order) and serves as the task's continuation. When the task final_suspends,
74 control returns to the run_async_trampoline which then invokes the appropriate handler.
75
76 For value-type allocators, the run_async_trampoline stores a frame_memory_resource
77 that wraps the allocator. For memory_resource*, it stores the pointer directly.
78
79 @tparam Ex The executor type.
80 @tparam Handlers The handler type (default_handler or handler_pair).
81 @tparam Alloc The allocator type (value type or memory_resource*).
82 */
83 template<class Ex, class Handlers, class Alloc>
84 struct run_async_trampoline
85 {
86 using invoke_fn = void(*)(void*, Handlers&);
87
88 struct promise_type
89 {
90 work_guard<Ex> wg_;
91 Handlers handlers_;
92 frame_memory_resource<Alloc> resource_;
93 io_env env_;
94 invoke_fn invoke_ = nullptr;
95 void* task_promise_ = nullptr;
96 std::coroutine_handle<> task_h_;
97
98 promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
99 : wg_(std::move(ex))
100 , handlers_(std::move(h))
101 , resource_(std::move(a))
102 {
103 }
104
105 static void* operator new(
106 std::size_t size, Ex const&, Handlers const&, Alloc a)
107 {
108 using byte_alloc = typename std::allocator_traits<Alloc>
109 ::template rebind_alloc<std::byte>;
110
111 constexpr auto footer_align =
112 (std::max)(alignof(dealloc_fn), alignof(Alloc));
113 auto padded = (size + footer_align - 1) & ~(footer_align - 1);
114 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
115
116 byte_alloc ba(std::move(a));
117 void* raw = ba.allocate(total);
118
119 auto* fn_loc = reinterpret_cast<dealloc_fn*>(
120 static_cast<char*>(raw) + padded);
121 *fn_loc = &dealloc_impl<byte_alloc>;
122
123 new (fn_loc + 1) byte_alloc(std::move(ba));
124
125 return raw;
126 }
127
128 static void operator delete(void* ptr, std::size_t size)
129 {
130 constexpr auto footer_align =
131 (std::max)(alignof(dealloc_fn), alignof(Alloc));
132 auto padded = (size + footer_align - 1) & ~(footer_align - 1);
133 auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
134
135 auto* fn = reinterpret_cast<dealloc_fn*>(
136 static_cast<char*>(ptr) + padded);
137 (*fn)(ptr, total);
138 }
139
140 std::pmr::memory_resource* get_resource() noexcept
141 {
142 return &resource_;
143 }
144
145 run_async_trampoline get_return_object() noexcept
146 {
147 return run_async_trampoline{
148 std::coroutine_handle<promise_type>::from_promise(*this)};
149 }
150
151 std::suspend_always initial_suspend() noexcept
152 {
153 return {};
154 }
155
156 std::suspend_never final_suspend() noexcept
157 {
158 return {};
159 }
160
161 void return_void() noexcept
162 {
163 }
164
165 void unhandled_exception() noexcept
166 {
167 }
168 };
169
170 std::coroutine_handle<promise_type> h_;
171
172 template<IoRunnable Task>
173 static void invoke_impl(void* p, Handlers& h)
174 {
175 using R = decltype(std::declval<Task&>().await_resume());
176 auto& promise = *static_cast<typename Task::promise_type*>(p);
177 if(promise.exception())
178 h(promise.exception());
179 else if constexpr(std::is_void_v<R>)
180 h();
181 else
182 h(std::move(promise.result()));
183 }
184 };
185
186 /** Specialization for memory_resource* - stores pointer directly.
187
188 This avoids double indirection when the user passes a memory_resource*.
189 */
190 template<class Ex, class Handlers>
191 struct run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
192 {
193 using invoke_fn = void(*)(void*, Handlers&);
194
195 struct promise_type
196 {
197 work_guard<Ex> wg_;
198 Handlers handlers_;
199 std::pmr::memory_resource* mr_;
200 io_env env_;
201 invoke_fn invoke_ = nullptr;
202 void* task_promise_ = nullptr;
203 std::coroutine_handle<> task_h_;
204
205 2014 promise_type(
206 Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
207 2014 : wg_(std::move(ex))
208 2014 , handlers_(std::move(h))
209 2014 , mr_(mr)
210 {
211 2014 }
212
213 2014 static void* operator new(
214 std::size_t size, Ex const&, Handlers const&,
215 std::pmr::memory_resource* mr)
216 {
217 2014 auto total = size + sizeof(mr);
218 2014 void* raw = mr->allocate(total, alignof(std::max_align_t));
219 2014 std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
220 2014 return raw;
221 }
222
223 2014 static void operator delete(void* ptr, std::size_t size)
224 {
225 std::pmr::memory_resource* mr;
226 2014 std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
227 2014 auto total = size + sizeof(mr);
228
1/1
✓ Branch 1 taken 2014 times.
2014 mr->deallocate(ptr, total, alignof(std::max_align_t));
229 2014 }
230
231 4028 std::pmr::memory_resource* get_resource() noexcept
232 {
233 4028 return mr_;
234 }
235
236 2014 run_async_trampoline get_return_object() noexcept
237 {
238 return run_async_trampoline{
239 2014 std::coroutine_handle<promise_type>::from_promise(*this)};
240 }
241
242 2014 std::suspend_always initial_suspend() noexcept
243 {
244 2014 return {};
245 }
246
247 2013 std::suspend_never final_suspend() noexcept
248 {
249 2013 return {};
250 }
251
252 2013 void return_void() noexcept
253 {
254 2013 }
255
256 void unhandled_exception() noexcept
257 {
258 }
259 };
260
261 std::coroutine_handle<promise_type> h_;
262
263 template<IoRunnable Task>
264 2013 static void invoke_impl(void* p, Handlers& h)
265 {
266 using R = decltype(std::declval<Task&>().await_resume());
267 2013 auto& promise = *static_cast<typename Task::promise_type*>(p);
268
2/2
✓ Branch 3 taken 720 times.
✓ Branch 4 taken 1293 times.
2013 if(promise.exception())
269
1/1
✓ Branch 2 taken 716 times.
720 h(promise.exception());
270 else if constexpr(std::is_void_v<R>)
271 1164 h();
272 else
273 129 h(std::move(promise.result()));
274 2013 }
275 };
276
277 /// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
278 template<class Ex, class Handlers, class Alloc>
279 run_async_trampoline<Ex, Handlers, Alloc>
280
2/2
✓ Branch 1 taken 183 times.
✓ Branch 1 taken 1831 times.
2014 make_trampoline(Ex, Handlers, Alloc)
281 {
282 // promise_type ctor steals the parameters
283 auto& p = co_await get_promise_awaiter<
284 typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
285
286 p.invoke_(p.task_promise_, p.handlers_);
287 p.task_h_.destroy();
288 4028 }
289
290 } // namespace detail
291
292 //----------------------------------------------------------
293 //
294 // run_async_wrapper
295 //
296 //----------------------------------------------------------
297
298 /** Wrapper returned by run_async that accepts a task for execution.
299
300 This wrapper holds the run_async_trampoline coroutine, executor, stop token,
301 and handlers. The run_async_trampoline is allocated when the wrapper is constructed
302 (before the task due to C++17 postfix evaluation order).
303
304 The rvalue ref-qualifier on `operator()` ensures the wrapper can only
305 be used as a temporary, preventing misuse that would violate LIFO ordering.
306
307 @tparam Ex The executor type satisfying the `Executor` concept.
308 @tparam Handlers The handler type (default_handler or handler_pair).
309 @tparam Alloc The allocator type (value type or memory_resource*).
310
311 @par Thread Safety
312 The wrapper itself should only be used from one thread. The handlers
313 may be invoked from any thread where the executor schedules work.
314
315 @par Example
316 @code
317 // Correct usage - wrapper is temporary
318 run_async(ex)(my_task());
319
320 // Compile error - cannot call operator() on lvalue
321 auto w = run_async(ex);
322 w(my_task()); // Error: operator() requires rvalue
323 @endcode
324
325 @see run_async
326 */
327 template<Executor Ex, class Handlers, class Alloc>
328 class [[nodiscard]] run_async_wrapper
329 {
330 detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
331 std::stop_token st_;
332 std::pmr::memory_resource* saved_tls_;
333
334 public:
335 /// Construct wrapper with executor, stop token, handlers, and allocator.
336 2014 run_async_wrapper(
337 Ex ex,
338 std::stop_token st,
339 Handlers h,
340 Alloc a) noexcept
341 2015 : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
342 2015 std::move(ex), std::move(h), std::move(a)))
343 2014 , st_(std::move(st))
344 2014 , saved_tls_(current_frame_allocator())
345 {
346 if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
347 {
348 static_assert(
349 std::is_nothrow_move_constructible_v<Alloc>,
350 "Allocator must be nothrow move constructible");
351 }
352 // Set TLS before task argument is evaluated
353 2014 current_frame_allocator() = tr_.h_.promise().get_resource();
354 2014 }
355
356 2014 ~run_async_wrapper()
357 {
358 // Restore TLS so stale pointer doesn't outlive
359 // the execution context that owns the resource.
360 2014 current_frame_allocator() = saved_tls_;
361 2014 }
362
363 // Non-copyable, non-movable (must be used immediately)
364 run_async_wrapper(run_async_wrapper const&) = delete;
365 run_async_wrapper(run_async_wrapper&&) = delete;
366 run_async_wrapper& operator=(run_async_wrapper const&) = delete;
367 run_async_wrapper& operator=(run_async_wrapper&&) = delete;
368
369 /** Launch the task for execution.
370
371 This operator accepts a task and launches it on the executor.
372 The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
373 correct LIFO destruction order.
374
375 The `io_env` constructed for the task is owned by the trampoline
376 coroutine and is guaranteed to outlive the task and all awaitables
377 in its chain. Awaitables may store `io_env const*` without concern
378 for dangling references.
379
380 @tparam Task The IoRunnable type.
381
382 @param t The task to execute. Ownership is transferred to the
383 run_async_trampoline which will destroy it after completion.
384 */
385 template<IoRunnable Task>
386 2014 void operator()(Task t) &&
387 {
388 2014 auto task_h = t.handle();
389 2014 auto& task_promise = task_h.promise();
390 2014 t.release();
391
392 2014 auto& p = tr_.h_.promise();
393
394 // Inject Task-specific invoke function
395 2014 p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
396 2014 p.task_promise_ = &task_promise;
397 2014 p.task_h_ = task_h;
398
399 // Setup task's continuation to return to run_async_trampoline
400 2014 task_promise.set_continuation(tr_.h_);
401 4028 p.env_ = {p.wg_.executor(), st_, p.get_resource()};
402 2014 task_promise.set_environment(&p.env_);
403
404 // Start task through executor
405
3/3
✓ Branch 3 taken 1898 times.
✓ Branch 4 taken 116 times.
✓ Branch 6 taken 1898 times.
2014 p.wg_.executor().dispatch(task_h).resume();
406 4028 }
407 };
408
409 //----------------------------------------------------------
410 //
411 // run_async Overloads
412 //
413 //----------------------------------------------------------
414
415 // Executor only (uses default recycling allocator)
416
417 /** Asynchronously launch a lazy task on the given executor.
418
419 Use this to start execution of a `task<T>` that was created lazily.
420 The returned wrapper must be immediately invoked with the task;
421 storing the wrapper and calling it later violates LIFO ordering.
422
423 Uses the default recycling frame allocator for coroutine frames.
424 With no handlers, the result is discarded and exceptions are rethrown.
425
426 @par Thread Safety
427 The wrapper and handlers may be called from any thread where the
428 executor schedules work.
429
430 @par Example
431 @code
432 run_async(ioc.get_executor())(my_task());
433 @endcode
434
435 @param ex The executor to execute the task on.
436
437 @return A wrapper that accepts a `task<T>` for immediate execution.
438
439 @see task
440 @see executor
441 */
442 template<Executor Ex>
443 [[nodiscard]] auto
444 2 run_async(Ex ex)
445 {
446 2 auto* mr = ex.context().get_frame_allocator();
447 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
448 2 std::move(ex),
449 4 std::stop_token{},
450 detail::default_handler{},
451 2 mr);
452 }
453
454 /** Asynchronously launch a lazy task with a result handler.
455
456 The handler `h1` is called with the task's result on success. If `h1`
457 is also invocable with `std::exception_ptr`, it handles exceptions too.
458 Otherwise, exceptions are rethrown.
459
460 @par Thread Safety
461 The handler may be called from any thread where the executor
462 schedules work.
463
464 @par Example
465 @code
466 // Handler for result only (exceptions rethrown)
467 run_async(ex, [](int result) {
468 std::cout << "Got: " << result << "\n";
469 })(compute_value());
470
471 // Overloaded handler for both result and exception
472 run_async(ex, overloaded{
473 [](int result) { std::cout << "Got: " << result << "\n"; },
474 [](std::exception_ptr) { std::cout << "Failed\n"; }
475 })(compute_value());
476 @endcode
477
478 @param ex The executor to execute the task on.
479 @param h1 The handler to invoke with the result (and optionally exception).
480
481 @return A wrapper that accepts a `task<T>` for immediate execution.
482
483 @see task
484 @see executor
485 */
486 template<Executor Ex, class H1>
487 [[nodiscard]] auto
488 34 run_async(Ex ex, H1 h1)
489 {
490 34 auto* mr = ex.context().get_frame_allocator();
491 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
492 34 std::move(ex),
493 34 std::stop_token{},
494 34 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
495 68 mr);
496 }
497
498 /** Asynchronously launch a lazy task with separate result and error handlers.
499
500 The handler `h1` is called with the task's result on success.
501 The handler `h2` is called with the exception_ptr on failure.
502
503 @par Thread Safety
504 The handlers may be called from any thread where the executor
505 schedules work.
506
507 @par Example
508 @code
509 run_async(ex,
510 [](int result) { std::cout << "Got: " << result << "\n"; },
511 [](std::exception_ptr ep) {
512 try { std::rethrow_exception(ep); }
513 catch (std::exception const& e) {
514 std::cout << "Error: " << e.what() << "\n";
515 }
516 }
517 )(compute_value());
518 @endcode
519
520 @param ex The executor to execute the task on.
521 @param h1 The handler to invoke with the result on success.
522 @param h2 The handler to invoke with the exception on failure.
523
524 @return A wrapper that accepts a `task<T>` for immediate execution.
525
526 @see task
527 @see executor
528 */
529 template<Executor Ex, class H1, class H2>
530 [[nodiscard]] auto
531 97 run_async(Ex ex, H1 h1, H2 h2)
532 {
533 97 auto* mr = ex.context().get_frame_allocator();
534 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
535 97 std::move(ex),
536 97 std::stop_token{},
537 97 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
538 194 mr);
539 1 }
540
541 // Ex + stop_token
542
543 /** Asynchronously launch a lazy task with stop token support.
544
545 The stop token is propagated to the task, enabling cooperative
546 cancellation. With no handlers, the result is discarded and
547 exceptions are rethrown.
548
549 @par Thread Safety
550 The wrapper may be called from any thread where the executor
551 schedules work.
552
553 @par Example
554 @code
555 std::stop_source source;
556 run_async(ex, source.get_token())(cancellable_task());
557 // Later: source.request_stop();
558 @endcode
559
560 @param ex The executor to execute the task on.
561 @param st The stop token for cooperative cancellation.
562
563 @return A wrapper that accepts a `task<T>` for immediate execution.
564
565 @see task
566 @see executor
567 */
568 template<Executor Ex>
569 [[nodiscard]] auto
570 1 run_async(Ex ex, std::stop_token st)
571 {
572 1 auto* mr = ex.context().get_frame_allocator();
573 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
574 1 std::move(ex),
575 1 std::move(st),
576 detail::default_handler{},
577 2 mr);
578 }
579
580 /** Asynchronously launch a lazy task with stop token and result handler.
581
582 The stop token is propagated to the task for cooperative cancellation.
583 The handler `h1` is called with the result on success, and optionally
584 with exception_ptr if it accepts that type.
585
586 @param ex The executor to execute the task on.
587 @param st The stop token for cooperative cancellation.
588 @param h1 The handler to invoke with the result (and optionally exception).
589
590 @return A wrapper that accepts a `task<T>` for immediate execution.
591
592 @see task
593 @see executor
594 */
595 template<Executor Ex, class H1>
596 [[nodiscard]] auto
597 1871 run_async(Ex ex, std::stop_token st, H1 h1)
598 {
599 1871 auto* mr = ex.context().get_frame_allocator();
600 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
601 1871 std::move(ex),
602 1871 std::move(st),
603 1871 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
604 3742 mr);
605 }
606
607 /** Asynchronously launch a lazy task with stop token and separate handlers.
608
609 The stop token is propagated to the task for cooperative cancellation.
610 The handler `h1` is called on success, `h2` on failure.
611
612 @param ex The executor to execute the task on.
613 @param st The stop token for cooperative cancellation.
614 @param h1 The handler to invoke with the result on success.
615 @param h2 The handler to invoke with the exception on failure.
616
617 @return A wrapper that accepts a `task<T>` for immediate execution.
618
619 @see task
620 @see executor
621 */
622 template<Executor Ex, class H1, class H2>
623 [[nodiscard]] auto
624 9 run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
625 {
626 9 auto* mr = ex.context().get_frame_allocator();
627 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
628 9 std::move(ex),
629 9 std::move(st),
630 9 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
631 18 mr);
632 }
633
634 // Ex + memory_resource*
635
636 /** Asynchronously launch a lazy task with custom memory resource.
637
638 The memory resource is used for coroutine frame allocation. The caller
639 is responsible for ensuring the memory resource outlives all tasks.
640
641 @param ex The executor to execute the task on.
642 @param mr The memory resource for frame allocation.
643
644 @return A wrapper that accepts a `task<T>` for immediate execution.
645
646 @see task
647 @see executor
648 */
649 template<Executor Ex>
650 [[nodiscard]] auto
651 run_async(Ex ex, std::pmr::memory_resource* mr)
652 {
653 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
654 std::move(ex),
655 std::stop_token{},
656 detail::default_handler{},
657 mr);
658 }
659
660 /** Asynchronously launch a lazy task with memory resource and handler.
661
662 @param ex The executor to execute the task on.
663 @param mr The memory resource for frame allocation.
664 @param h1 The handler to invoke with the result (and optionally exception).
665
666 @return A wrapper that accepts a `task<T>` for immediate execution.
667
668 @see task
669 @see executor
670 */
671 template<Executor Ex, class H1>
672 [[nodiscard]] auto
673 run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
674 {
675 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
676 std::move(ex),
677 std::stop_token{},
678 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
679 mr);
680 }
681
682 /** Asynchronously launch a lazy task with memory resource and handlers.
683
684 @param ex The executor to execute the task on.
685 @param mr The memory resource for frame allocation.
686 @param h1 The handler to invoke with the result on success.
687 @param h2 The handler to invoke with the exception on failure.
688
689 @return A wrapper that accepts a `task<T>` for immediate execution.
690
691 @see task
692 @see executor
693 */
694 template<Executor Ex, class H1, class H2>
695 [[nodiscard]] auto
696 run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
697 {
698 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
699 std::move(ex),
700 std::stop_token{},
701 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
702 mr);
703 }
704
705 // Ex + stop_token + memory_resource*
706
707 /** Asynchronously launch a lazy task with stop token and memory resource.
708
709 @param ex The executor to execute the task on.
710 @param st The stop token for cooperative cancellation.
711 @param mr The memory resource for frame allocation.
712
713 @return A wrapper that accepts a `task<T>` for immediate execution.
714
715 @see task
716 @see executor
717 */
718 template<Executor Ex>
719 [[nodiscard]] auto
720 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
721 {
722 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
723 std::move(ex),
724 std::move(st),
725 detail::default_handler{},
726 mr);
727 }
728
729 /** Asynchronously launch a lazy task with stop token, memory resource, and handler.
730
731 @param ex The executor to execute the task on.
732 @param st The stop token for cooperative cancellation.
733 @param mr The memory resource for frame allocation.
734 @param h1 The handler to invoke with the result (and optionally exception).
735
736 @return A wrapper that accepts a `task<T>` for immediate execution.
737
738 @see task
739 @see executor
740 */
741 template<Executor Ex, class H1>
742 [[nodiscard]] auto
743 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
744 {
745 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
746 std::move(ex),
747 std::move(st),
748 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
749 mr);
750 }
751
752 /** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
753
754 @param ex The executor to execute the task on.
755 @param st The stop token for cooperative cancellation.
756 @param mr The memory resource for frame allocation.
757 @param h1 The handler to invoke with the result on success.
758 @param h2 The handler to invoke with the exception on failure.
759
760 @return A wrapper that accepts a `task<T>` for immediate execution.
761
762 @see task
763 @see executor
764 */
765 template<Executor Ex, class H1, class H2>
766 [[nodiscard]] auto
767 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
768 {
769 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
770 std::move(ex),
771 std::move(st),
772 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
773 mr);
774 }
775
776 // Ex + standard Allocator (value type)
777
778 /** Asynchronously launch a lazy task with custom allocator.
779
780 The allocator is wrapped in a frame_memory_resource and stored in the
781 run_async_trampoline, ensuring it outlives all coroutine frames.
782
783 @param ex The executor to execute the task on.
784 @param alloc The allocator for frame allocation (copied and stored).
785
786 @return A wrapper that accepts a `task<T>` for immediate execution.
787
788 @see task
789 @see executor
790 */
791 template<Executor Ex, detail::Allocator Alloc>
792 [[nodiscard]] auto
793 run_async(Ex ex, Alloc alloc)
794 {
795 return run_async_wrapper<Ex, detail::default_handler, Alloc>(
796 std::move(ex),
797 std::stop_token{},
798 detail::default_handler{},
799 std::move(alloc));
800 }
801
802 /** Asynchronously launch a lazy task with allocator and handler.
803
804 @param ex The executor to execute the task on.
805 @param alloc The allocator for frame allocation (copied and stored).
806 @param h1 The handler to invoke with the result (and optionally exception).
807
808 @return A wrapper that accepts a `task<T>` for immediate execution.
809
810 @see task
811 @see executor
812 */
813 template<Executor Ex, detail::Allocator Alloc, class H1>
814 [[nodiscard]] auto
815 run_async(Ex ex, Alloc alloc, H1 h1)
816 {
817 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
818 std::move(ex),
819 std::stop_token{},
820 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
821 std::move(alloc));
822 }
823
824 /** Asynchronously launch a lazy task with allocator and handlers.
825
826 @param ex The executor to execute the task on.
827 @param alloc The allocator for frame allocation (copied and stored).
828 @param h1 The handler to invoke with the result on success.
829 @param h2 The handler to invoke with the exception on failure.
830
831 @return A wrapper that accepts a `task<T>` for immediate execution.
832
833 @see task
834 @see executor
835 */
836 template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
837 [[nodiscard]] auto
838 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
839 {
840 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
841 std::move(ex),
842 std::stop_token{},
843 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
844 std::move(alloc));
845 }
846
847 // Ex + stop_token + standard Allocator
848
849 /** Asynchronously launch a lazy task with stop token and allocator.
850
851 @param ex The executor to execute the task on.
852 @param st The stop token for cooperative cancellation.
853 @param alloc The allocator for frame allocation (copied and stored).
854
855 @return A wrapper that accepts a `task<T>` for immediate execution.
856
857 @see task
858 @see executor
859 */
860 template<Executor Ex, detail::Allocator Alloc>
861 [[nodiscard]] auto
862 run_async(Ex ex, std::stop_token st, Alloc alloc)
863 {
864 return run_async_wrapper<Ex, detail::default_handler, Alloc>(
865 std::move(ex),
866 std::move(st),
867 detail::default_handler{},
868 std::move(alloc));
869 }
870
871 /** Asynchronously launch a lazy task with stop token, allocator, and handler.
872
873 @param ex The executor to execute the task on.
874 @param st The stop token for cooperative cancellation.
875 @param alloc The allocator for frame allocation (copied and stored).
876 @param h1 The handler to invoke with the result (and optionally exception).
877
878 @return A wrapper that accepts a `task<T>` for immediate execution.
879
880 @see task
881 @see executor
882 */
883 template<Executor Ex, detail::Allocator Alloc, class H1>
884 [[nodiscard]] auto
885 run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
886 {
887 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
888 std::move(ex),
889 std::move(st),
890 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
891 std::move(alloc));
892 }
893
894 /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
895
896 @param ex The executor to execute the task on.
897 @param st The stop token for cooperative cancellation.
898 @param alloc The allocator for frame allocation (copied and stored).
899 @param h1 The handler to invoke with the result on success.
900 @param h2 The handler to invoke with the exception on failure.
901
902 @return A wrapper that accepts a `task<T>` for immediate execution.
903
904 @see task
905 @see executor
906 */
907 template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
908 [[nodiscard]] auto
909 run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
910 {
911 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
912 std::move(ex),
913 std::move(st),
914 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
915 std::move(alloc));
916 }
917
918 } // namespace capy
919 } // namespace boost
920
921 #endif
922