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