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

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