include/boost/capy/ex/run_async.hpp

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