include/boost/capy/ex/run_async.hpp

85.2% Lines (98/115) 92.7% Functions (3264/3521)
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 3074x bool await_ready() const noexcept { return false; }
58
59 3074x bool await_suspend(std::coroutine_handle<Promise> h) noexcept
60 {
61 3074x p_ = &h.promise();
62 3074x return false;
63 }
64
65 3074x Promise& await_resume() const noexcept
66 {
67 3074x 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 3216x promise_type(
207 Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
208 3216x : wg_(std::move(ex))
209 3216x , handlers_(std::move(h))
210 3216x , mr_(mr)
211 {
212 3216x }
213
214 3216x static void* operator new(
215 std::size_t size, Ex const&, Handlers const&,
216 std::pmr::memory_resource* mr)
217 {
218 3216x auto total = size + sizeof(mr);
219 3216x void* raw = mr->allocate(total, alignof(std::max_align_t));
220 3216x std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
221 3216x return raw;
222 }
223
224 3216x static void operator delete(void* ptr, std::size_t size)
225 {
226 std::pmr::memory_resource* mr;
227 3216x std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
228 3216x auto total = size + sizeof(mr);
229 3216x mr->deallocate(ptr, total, alignof(std::max_align_t));
230 3216x }
231
232 6432x std::pmr::memory_resource* get_resource() noexcept
233 {
234 6432x return mr_;
235 }
236
237 3216x run_async_trampoline get_return_object() noexcept
238 {
239 return run_async_trampoline{
240 3216x std::coroutine_handle<promise_type>::from_promise(*this)};
241 }
242
243 3216x std::suspend_always initial_suspend() noexcept
244 {
245 3216x return {};
246 }
247
248 3074x std::suspend_never final_suspend() noexcept
249 {
250 3074x return {};
251 }
252
253 3074x void return_void() noexcept
254 {
255 3074x }
256
257 void unhandled_exception() noexcept
258 {
259 }
260 };
261
262 std::coroutine_handle<promise_type> h_;
263
264 template<IoRunnable Task>
265 3074x static void invoke_impl(void* p, Handlers& h)
266 {
267 using R = decltype(std::declval<Task&>().await_resume());
268 3074x auto& promise = *static_cast<typename Task::promise_type*>(p);
269 3074x if(promise.exception())
270 1028x h(promise.exception());
271 else if constexpr(std::is_void_v<R>)
272 1896x h();
273 else
274 150x h(std::move(promise.result()));
275 3074x }
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 3216x 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 6432x }
290
291 } // namespace detail
292
293 /** Wrapper returned by run_async that accepts a task for execution.
294
295 This wrapper holds the run_async_trampoline coroutine, executor, stop token,
296 and handlers. The run_async_trampoline is allocated when the wrapper is constructed
297 (before the task due to C++17 postfix evaluation order).
298
299 The rvalue ref-qualifier on `operator()` ensures the wrapper can only
300 be used as a temporary, preventing misuse that would violate LIFO ordering.
301
302 @tparam Ex The executor type satisfying the `Executor` concept.
303 @tparam Handlers The handler type (default_handler or handler_pair).
304 @tparam Alloc The allocator type (value type or memory_resource*).
305
306 @par Thread Safety
307 The wrapper itself should only be used from one thread. The handlers
308 may be invoked from any thread where the executor schedules work.
309
310 @par Example
311 @code
312 // Correct usage - wrapper is temporary
313 run_async(ex)(my_task());
314
315 // Compile error - cannot call operator() on lvalue
316 auto w = run_async(ex);
317 w(my_task()); // Error: operator() requires rvalue
318 @endcode
319
320 @see run_async
321 */
322 template<Executor Ex, class Handlers, class Alloc>
323 class [[nodiscard]] run_async_wrapper
324 {
325 detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
326 std::stop_token st_;
327 std::pmr::memory_resource* saved_tls_;
328
329 public:
330 /// Construct wrapper with executor, stop token, handlers, and allocator.
331 3216x run_async_wrapper(
332 Ex ex,
333 std::stop_token st,
334 Handlers h,
335 Alloc a) noexcept
336 3217x : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
337 3217x std::move(ex), std::move(h), std::move(a)))
338 3216x , st_(std::move(st))
339 3216x , saved_tls_(get_current_frame_allocator())
340 {
341 if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
342 {
343 static_assert(
344 std::is_nothrow_move_constructible_v<Alloc>,
345 "Allocator must be nothrow move constructible");
346 }
347 // Set TLS before task argument is evaluated
348 3216x set_current_frame_allocator(tr_.h_.promise().get_resource());
349 3216x }
350
351 3216x ~run_async_wrapper()
352 {
353 // Restore TLS so stale pointer doesn't outlive
354 // the execution context that owns the resource.
355 3216x set_current_frame_allocator(saved_tls_);
356 3216x }
357
358 // Non-copyable, non-movable (must be used immediately)
359 run_async_wrapper(run_async_wrapper const&) = delete;
360 run_async_wrapper(run_async_wrapper&&) = delete;
361 run_async_wrapper& operator=(run_async_wrapper const&) = delete;
362 run_async_wrapper& operator=(run_async_wrapper&&) = delete;
363
364 /** Launch the task for execution.
365
366 This operator accepts a task and launches it on the executor.
367 The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
368 correct LIFO destruction order.
369
370 The `io_env` constructed for the task is owned by the trampoline
371 coroutine and is guaranteed to outlive the task and all awaitables
372 in its chain. Awaitables may store `io_env const*` without concern
373 for dangling references.
374
375 @tparam Task The IoRunnable type.
376
377 @param t The task to execute. Ownership is transferred to the
378 run_async_trampoline which will destroy it after completion.
379 */
380 template<IoRunnable Task>
381 3216x void operator()(Task t) &&
382 {
383 3216x auto task_h = t.handle();
384 3216x auto& task_promise = task_h.promise();
385 3216x t.release();
386
387 3216x auto& p = tr_.h_.promise();
388
389 // Inject Task-specific invoke function
390 3216x p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
391 3216x p.task_promise_ = &task_promise;
392 3216x p.task_h_ = task_h;
393
394 // Setup task's continuation to return to run_async_trampoline
395 3216x task_promise.set_continuation(tr_.h_);
396 6432x p.env_ = {p.wg_.executor(), st_, p.get_resource()};
397 3216x task_promise.set_environment(&p.env_);
398
399 // Start task through executor
400 3216x p.wg_.executor().dispatch(task_h).resume();
401 6432x }
402 };
403
404 // Executor only (uses default recycling allocator)
405
406 /** Asynchronously launch a lazy task on the given executor.
407
408 Use this to start execution of a `task<T>` that was created lazily.
409 The returned wrapper must be immediately invoked with the task;
410 storing the wrapper and calling it later violates LIFO ordering.
411
412 Uses the default recycling frame allocator for coroutine frames.
413 With no handlers, the result is discarded and exceptions are rethrown.
414
415 @par Thread Safety
416 The wrapper and handlers may be called from any thread where the
417 executor schedules work.
418
419 @par Example
420 @code
421 run_async(ioc.get_executor())(my_task());
422 @endcode
423
424 @param ex The executor to execute the task on.
425
426 @return A wrapper that accepts a `task<T>` for immediate execution.
427
428 @see task
429 @see executor
430 */
431 template<Executor Ex>
432 [[nodiscard]] auto
433 2x run_async(Ex ex)
434 {
435 2x auto* mr = ex.context().get_frame_allocator();
436 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
437 2x std::move(ex),
438 4x std::stop_token{},
439 detail::default_handler{},
440 2x mr);
441 }
442
443 /** Asynchronously launch a lazy task with a result handler.
444
445 The handler `h1` is called with the task's result on success. If `h1`
446 is also invocable with `std::exception_ptr`, it handles exceptions too.
447 Otherwise, exceptions are rethrown.
448
449 @par Thread Safety
450 The handler may be called from any thread where the executor
451 schedules work.
452
453 @par Example
454 @code
455 // Handler for result only (exceptions rethrown)
456 run_async(ex, [](int result) {
457 std::cout << "Got: " << result << "\n";
458 })(compute_value());
459
460 // Overloaded handler for both result and exception
461 run_async(ex, overloaded{
462 [](int result) { std::cout << "Got: " << result << "\n"; },
463 [](std::exception_ptr) { std::cout << "Failed\n"; }
464 })(compute_value());
465 @endcode
466
467 @param ex The executor to execute the task on.
468 @param h1 The handler to invoke with the result (and optionally exception).
469
470 @return A wrapper that accepts a `task<T>` for immediate execution.
471
472 @see task
473 @see executor
474 */
475 template<Executor Ex, class H1>
476 [[nodiscard]] auto
477 88x run_async(Ex ex, H1 h1)
478 {
479 88x auto* mr = ex.context().get_frame_allocator();
480 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
481 88x std::move(ex),
482 88x std::stop_token{},
483 88x detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
484 176x mr);
485 }
486
487 /** Asynchronously launch a lazy task with separate result and error handlers.
488
489 The handler `h1` is called with the task's result on success.
490 The handler `h2` is called with the exception_ptr on failure.
491
492 @par Thread Safety
493 The handlers may be called from any thread where the executor
494 schedules work.
495
496 @par Example
497 @code
498 run_async(ex,
499 [](int result) { std::cout << "Got: " << result << "\n"; },
500 [](std::exception_ptr ep) {
501 try { std::rethrow_exception(ep); }
502 catch (std::exception const& e) {
503 std::cout << "Error: " << e.what() << "\n";
504 }
505 }
506 )(compute_value());
507 @endcode
508
509 @param ex The executor to execute the task on.
510 @param h1 The handler to invoke with the result on success.
511 @param h2 The handler to invoke with the exception on failure.
512
513 @return A wrapper that accepts a `task<T>` for immediate execution.
514
515 @see task
516 @see executor
517 */
518 template<Executor Ex, class H1, class H2>
519 [[nodiscard]] auto
520 99x run_async(Ex ex, H1 h1, H2 h2)
521 {
522 99x auto* mr = ex.context().get_frame_allocator();
523 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
524 99x std::move(ex),
525 99x std::stop_token{},
526 99x detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
527 198x mr);
528 1x }
529
530 // Ex + stop_token
531
532 /** Asynchronously launch a lazy task with stop token support.
533
534 The stop token is propagated to the task, enabling cooperative
535 cancellation. With no handlers, the result is discarded and
536 exceptions are rethrown.
537
538 @par Thread Safety
539 The wrapper may be called from any thread where the executor
540 schedules work.
541
542 @par Example
543 @code
544 std::stop_source source;
545 run_async(ex, source.get_token())(cancellable_task());
546 // Later: source.request_stop();
547 @endcode
548
549 @param ex The executor to execute the task on.
550 @param st The stop token for cooperative cancellation.
551
552 @return A wrapper that accepts a `task<T>` for immediate execution.
553
554 @see task
555 @see executor
556 */
557 template<Executor Ex>
558 [[nodiscard]] auto
559 255x run_async(Ex ex, std::stop_token st)
560 {
561 255x auto* mr = ex.context().get_frame_allocator();
562 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
563 255x std::move(ex),
564 255x std::move(st),
565 detail::default_handler{},
566 510x mr);
567 }
568
569 /** Asynchronously launch a lazy task with stop token and result handler.
570
571 The stop token is propagated to the task for cooperative cancellation.
572 The handler `h1` is called with the result on success, and optionally
573 with exception_ptr if it accepts that type.
574
575 @param ex The executor to execute the task on.
576 @param st The stop token for cooperative cancellation.
577 @param h1 The handler to invoke with the result (and optionally exception).
578
579 @return A wrapper that accepts a `task<T>` for immediate execution.
580
581 @see task
582 @see executor
583 */
584 template<Executor Ex, class H1>
585 [[nodiscard]] auto
586 2763x run_async(Ex ex, std::stop_token st, H1 h1)
587 {
588 2763x auto* mr = ex.context().get_frame_allocator();
589 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
590 2763x std::move(ex),
591 2763x std::move(st),
592 2763x detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
593 5526x mr);
594 }
595
596 /** Asynchronously launch a lazy task with stop token and separate handlers.
597
598 The stop token is propagated to the task for cooperative cancellation.
599 The handler `h1` is called on success, `h2` on failure.
600
601 @param ex The executor to execute the task on.
602 @param st The stop token for cooperative cancellation.
603 @param h1 The handler to invoke with the result on success.
604 @param h2 The handler to invoke with the exception on failure.
605
606 @return A wrapper that accepts a `task<T>` for immediate execution.
607
608 @see task
609 @see executor
610 */
611 template<Executor Ex, class H1, class H2>
612 [[nodiscard]] auto
613 9x run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
614 {
615 9x auto* mr = ex.context().get_frame_allocator();
616 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
617 9x std::move(ex),
618 9x std::move(st),
619 9x detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
620 18x mr);
621 }
622
623 // Ex + memory_resource*
624
625 /** Asynchronously launch a lazy task with custom memory resource.
626
627 The memory resource is used for coroutine frame allocation. The caller
628 is responsible for ensuring the memory resource outlives all tasks.
629
630 @param ex The executor to execute the task on.
631 @param mr The memory resource for frame allocation.
632
633 @return A wrapper that accepts a `task<T>` for immediate execution.
634
635 @see task
636 @see executor
637 */
638 template<Executor Ex>
639 [[nodiscard]] auto
640 run_async(Ex ex, std::pmr::memory_resource* mr)
641 {
642 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
643 std::move(ex),
644 std::stop_token{},
645 detail::default_handler{},
646 mr);
647 }
648
649 /** Asynchronously launch a lazy task with memory resource and handler.
650
651 @param ex The executor to execute the task on.
652 @param mr The memory resource for frame allocation.
653 @param h1 The handler to invoke with the result (and optionally exception).
654
655 @return A wrapper that accepts a `task<T>` for immediate execution.
656
657 @see task
658 @see executor
659 */
660 template<Executor Ex, class H1>
661 [[nodiscard]] auto
662 run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
663 {
664 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
665 std::move(ex),
666 std::stop_token{},
667 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
668 mr);
669 }
670
671 /** Asynchronously launch a lazy task with memory resource and handlers.
672
673 @param ex The executor to execute the task on.
674 @param mr The memory resource for frame allocation.
675 @param h1 The handler to invoke with the result on success.
676 @param h2 The handler to invoke with the exception on failure.
677
678 @return A wrapper that accepts a `task<T>` for immediate execution.
679
680 @see task
681 @see executor
682 */
683 template<Executor Ex, class H1, class H2>
684 [[nodiscard]] auto
685 run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
686 {
687 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
688 std::move(ex),
689 std::stop_token{},
690 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
691 mr);
692 }
693
694 // Ex + stop_token + memory_resource*
695
696 /** Asynchronously launch a lazy task with stop token and memory resource.
697
698 @param ex The executor to execute the task on.
699 @param st The stop token for cooperative cancellation.
700 @param mr The memory resource for frame allocation.
701
702 @return A wrapper that accepts a `task<T>` for immediate execution.
703
704 @see task
705 @see executor
706 */
707 template<Executor Ex>
708 [[nodiscard]] auto
709 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
710 {
711 return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
712 std::move(ex),
713 std::move(st),
714 detail::default_handler{},
715 mr);
716 }
717
718 /** Asynchronously launch a lazy task with stop token, memory resource, and handler.
719
720 @param ex The executor to execute the task on.
721 @param st The stop token for cooperative cancellation.
722 @param mr The memory resource for frame allocation.
723 @param h1 The handler to invoke with the result (and optionally exception).
724
725 @return A wrapper that accepts a `task<T>` for immediate execution.
726
727 @see task
728 @see executor
729 */
730 template<Executor Ex, class H1>
731 [[nodiscard]] auto
732 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
733 {
734 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
735 std::move(ex),
736 std::move(st),
737 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
738 mr);
739 }
740
741 /** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
742
743 @param ex The executor to execute the task on.
744 @param st The stop token for cooperative cancellation.
745 @param mr The memory resource for frame allocation.
746 @param h1 The handler to invoke with the result on success.
747 @param h2 The handler to invoke with the exception on failure.
748
749 @return A wrapper that accepts a `task<T>` for immediate execution.
750
751 @see task
752 @see executor
753 */
754 template<Executor Ex, class H1, class H2>
755 [[nodiscard]] auto
756 run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
757 {
758 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
759 std::move(ex),
760 std::move(st),
761 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
762 mr);
763 }
764
765 // Ex + standard Allocator (value type)
766
767 /** Asynchronously launch a lazy task with custom allocator.
768
769 The allocator is wrapped in a frame_memory_resource and stored in the
770 run_async_trampoline, ensuring it outlives all coroutine frames.
771
772 @param ex The executor to execute the task on.
773 @param alloc The allocator for frame allocation (copied and stored).
774
775 @return A wrapper that accepts a `task<T>` for immediate execution.
776
777 @see task
778 @see executor
779 */
780 template<Executor Ex, detail::Allocator Alloc>
781 [[nodiscard]] auto
782 run_async(Ex ex, Alloc alloc)
783 {
784 return run_async_wrapper<Ex, detail::default_handler, Alloc>(
785 std::move(ex),
786 std::stop_token{},
787 detail::default_handler{},
788 std::move(alloc));
789 }
790
791 /** Asynchronously launch a lazy task with allocator and handler.
792
793 @param ex The executor to execute the task on.
794 @param alloc The allocator for frame allocation (copied and stored).
795 @param h1 The handler to invoke with the result (and optionally exception).
796
797 @return A wrapper that accepts a `task<T>` for immediate execution.
798
799 @see task
800 @see executor
801 */
802 template<Executor Ex, detail::Allocator Alloc, class H1>
803 [[nodiscard]] auto
804 run_async(Ex ex, Alloc alloc, H1 h1)
805 {
806 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
807 std::move(ex),
808 std::stop_token{},
809 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
810 std::move(alloc));
811 }
812
813 /** Asynchronously launch a lazy task with allocator and handlers.
814
815 @param ex The executor to execute the task on.
816 @param alloc The allocator for frame allocation (copied and stored).
817 @param h1 The handler to invoke with the result on success.
818 @param h2 The handler to invoke with the exception on failure.
819
820 @return A wrapper that accepts a `task<T>` for immediate execution.
821
822 @see task
823 @see executor
824 */
825 template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
826 [[nodiscard]] auto
827 run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
828 {
829 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
830 std::move(ex),
831 std::stop_token{},
832 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
833 std::move(alloc));
834 }
835
836 // Ex + stop_token + standard Allocator
837
838 /** Asynchronously launch a lazy task with stop token and allocator.
839
840 @param ex The executor to execute the task on.
841 @param st The stop token for cooperative cancellation.
842 @param alloc The allocator for frame allocation (copied and stored).
843
844 @return A wrapper that accepts a `task<T>` for immediate execution.
845
846 @see task
847 @see executor
848 */
849 template<Executor Ex, detail::Allocator Alloc>
850 [[nodiscard]] auto
851 run_async(Ex ex, std::stop_token st, Alloc alloc)
852 {
853 return run_async_wrapper<Ex, detail::default_handler, Alloc>(
854 std::move(ex),
855 std::move(st),
856 detail::default_handler{},
857 std::move(alloc));
858 }
859
860 /** Asynchronously launch a lazy task with stop token, allocator, and handler.
861
862 @param ex The executor to execute the task on.
863 @param st The stop token for cooperative cancellation.
864 @param alloc The allocator for frame allocation (copied and stored).
865 @param h1 The handler to invoke with the result (and optionally exception).
866
867 @return A wrapper that accepts a `task<T>` for immediate execution.
868
869 @see task
870 @see executor
871 */
872 template<Executor Ex, detail::Allocator Alloc, class H1>
873 [[nodiscard]] auto
874 run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
875 {
876 return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
877 std::move(ex),
878 std::move(st),
879 detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
880 std::move(alloc));
881 }
882
883 /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
884
885 @param ex The executor to execute the task on.
886 @param st The stop token for cooperative cancellation.
887 @param alloc The allocator for frame allocation (copied and stored).
888 @param h1 The handler to invoke with the result on success.
889 @param h2 The handler to invoke with the exception on failure.
890
891 @return A wrapper that accepts a `task<T>` for immediate execution.
892
893 @see task
894 @see executor
895 */
896 template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
897 [[nodiscard]] auto
898 run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
899 {
900 return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
901 std::move(ex),
902 std::move(st),
903 detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
904 std::move(alloc));
905 }
906
907 } // namespace capy
908 } // namespace boost
909
910 #endif
911