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