include/boost/capy/ex/run_async.hpp

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