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