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

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