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