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