Line data 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 0 : static void operator delete(void* ptr, std::size_t size)
128 : {
129 0 : constexpr auto footer_align =
130 : (std::max)(alignof(dealloc_fn), alignof(Alloc));
131 0 : auto padded = (size + footer_align - 1) & ~(footer_align - 1);
132 0 : auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
133 :
134 0 : auto* fn = reinterpret_cast<dealloc_fn*>(
135 : static_cast<char*>(ptr) + padded);
136 0 : (*fn)(ptr, total);
137 0 : }
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 0 : std::suspend_always initial_suspend() noexcept
151 : {
152 0 : return {};
153 : }
154 :
155 0 : std::suspend_never final_suspend() noexcept
156 : {
157 0 : return {};
158 : }
159 :
160 0 : void return_void() noexcept
161 : {
162 0 : }
163 :
164 0 : void unhandled_exception() noexcept
165 : {
166 0 : }
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 0 : void unhandled_exception() noexcept
256 : {
257 0 : }
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 2013 : if(promise.exception())
268 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 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 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
|