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