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