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

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