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

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