libs/capy/include/boost/capy/ex/run_async.hpp

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