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 3135 : bool await_ready() const noexcept { return false; }
58 :
59 3135 : bool await_suspend(std::coroutine_handle<Promise> h) noexcept
60 : {
61 3135 : p_ = &h.promise();
62 3135 : return false;
63 : }
64 :
65 3135 : Promise& await_resume() const noexcept
66 : {
67 3135 : 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 3135 : std::suspend_never final_suspend() noexcept
257 : {
258 3135 : return {};
259 : }
260 :
261 3130 : void return_void() noexcept
262 : {
263 3130 : }
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 3135 : static void invoke_impl(void* p, Handlers& h)
274 : {
275 : using R = decltype(std::declval<Task&>().await_resume());
276 3135 : auto& promise = *static_cast<typename Task::promise_type*>(p);
277 3135 : if(promise.exception())
278 1051 : h(promise.exception());
279 : else if constexpr(std::is_void_v<R>)
280 1932 : h();
281 : else
282 152 : h(std::move(promise.result()));
283 3130 : }
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 3135 : ~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 3272 : p.task_cont_.h = task_h;
416 3272 : p.wg_.executor().dispatch(p.task_cont_).resume();
417 6544 : }
418 : };
419 :
420 : // Executor only (uses default recycling allocator)
421 :
422 : /** Asynchronously launch a lazy task on the given executor.
423 :
424 : Use this to start execution of a `task<T>` that was created lazily.
425 : The returned wrapper must be immediately invoked with the task;
426 : storing the wrapper and calling it later violates LIFO ordering.
427 :
428 : Uses the default recycling frame allocator for coroutine frames.
429 : With no handlers, the result is discarded and exceptions are rethrown.
430 :
431 : @par Thread Safety
432 : The wrapper and handlers may be called from any thread where the
433 : executor schedules work.
434 :
435 : @par Example
436 : @code
437 : run_async(ioc.get_executor())(my_task());
438 : @endcode
439 :
440 : @param ex The executor to execute the task on.
441 :
442 : @return A wrapper that accepts a `task<T>` for immediate execution.
443 :
444 : @see task
445 : @see executor
446 : */
447 : template<Executor Ex>
448 : [[nodiscard]] auto
449 2 : run_async(Ex ex)
450 : {
451 2 : auto* mr = ex.context().get_frame_allocator();
452 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
453 2 : std::move(ex),
454 4 : std::stop_token{},
455 : detail::default_handler{},
456 2 : mr);
457 : }
458 :
459 : /** Asynchronously launch a lazy task with a result handler.
460 :
461 : The handler `h1` is called with the task's result on success. If `h1`
462 : is also invocable with `std::exception_ptr`, it handles exceptions too.
463 : Otherwise, exceptions are rethrown.
464 :
465 : @par Thread Safety
466 : The handler may be called from any thread where the executor
467 : schedules work.
468 :
469 : @par Example
470 : @code
471 : // Handler for result only (exceptions rethrown)
472 : run_async(ex, [](int result) {
473 : std::cout << "Got: " << result << "\n";
474 : })(compute_value());
475 :
476 : // Overloaded handler for both result and exception
477 : run_async(ex, overloaded{
478 : [](int result) { std::cout << "Got: " << result << "\n"; },
479 : [](std::exception_ptr) { std::cout << "Failed\n"; }
480 : })(compute_value());
481 : @endcode
482 :
483 : @param ex The executor to execute the task on.
484 : @param h1 The handler to invoke with the result (and optionally exception).
485 :
486 : @return A wrapper that accepts a `task<T>` for immediate execution.
487 :
488 : @see task
489 : @see executor
490 : */
491 : template<Executor Ex, class H1>
492 : [[nodiscard]] auto
493 89 : run_async(Ex ex, H1 h1)
494 : {
495 89 : auto* mr = ex.context().get_frame_allocator();
496 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
497 89 : std::move(ex),
498 89 : std::stop_token{},
499 89 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
500 178 : mr);
501 : }
502 :
503 : /** Asynchronously launch a lazy task with separate result and error handlers.
504 :
505 : The handler `h1` is called with the task's result on success.
506 : The handler `h2` is called with the exception_ptr on failure.
507 :
508 : @par Thread Safety
509 : The handlers may be called from any thread where the executor
510 : schedules work.
511 :
512 : @par Example
513 : @code
514 : run_async(ex,
515 : [](int result) { std::cout << "Got: " << result << "\n"; },
516 : [](std::exception_ptr ep) {
517 : try { std::rethrow_exception(ep); }
518 : catch (std::exception const& e) {
519 : std::cout << "Error: " << e.what() << "\n";
520 : }
521 : }
522 : )(compute_value());
523 : @endcode
524 :
525 : @param ex The executor to execute the task on.
526 : @param h1 The handler to invoke with the result on success.
527 : @param h2 The handler to invoke with the exception on failure.
528 :
529 : @return A wrapper that accepts a `task<T>` for immediate execution.
530 :
531 : @see task
532 : @see executor
533 : */
534 : template<Executor Ex, class H1, class H2>
535 : [[nodiscard]] auto
536 111 : run_async(Ex ex, H1 h1, H2 h2)
537 : {
538 111 : auto* mr = ex.context().get_frame_allocator();
539 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
540 111 : std::move(ex),
541 111 : std::stop_token{},
542 111 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
543 222 : mr);
544 1 : }
545 :
546 : // Ex + stop_token
547 :
548 : /** Asynchronously launch a lazy task with stop token support.
549 :
550 : The stop token is propagated to the task, enabling cooperative
551 : cancellation. With no handlers, the result is discarded and
552 : exceptions are rethrown.
553 :
554 : @par Thread Safety
555 : The wrapper may be called from any thread where the executor
556 : schedules work.
557 :
558 : @par Example
559 : @code
560 : std::stop_source source;
561 : run_async(ex, source.get_token())(cancellable_task());
562 : // Later: source.request_stop();
563 : @endcode
564 :
565 : @param ex The executor to execute the task on.
566 : @param st The stop token for cooperative cancellation.
567 :
568 : @return A wrapper that accepts a `task<T>` for immediate execution.
569 :
570 : @see task
571 : @see executor
572 : */
573 : template<Executor Ex>
574 : [[nodiscard]] auto
575 260 : run_async(Ex ex, std::stop_token st)
576 : {
577 260 : auto* mr = ex.context().get_frame_allocator();
578 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
579 260 : std::move(ex),
580 260 : std::move(st),
581 : detail::default_handler{},
582 520 : mr);
583 : }
584 :
585 : /** Asynchronously launch a lazy task with stop token and result handler.
586 :
587 : The stop token is propagated to the task for cooperative cancellation.
588 : The handler `h1` is called with the result on success, and optionally
589 : with exception_ptr if it accepts that type.
590 :
591 : @param ex The executor to execute the task on.
592 : @param st The stop token for cooperative cancellation.
593 : @param h1 The handler to invoke with the result (and optionally exception).
594 :
595 : @return A wrapper that accepts a `task<T>` for immediate execution.
596 :
597 : @see task
598 : @see executor
599 : */
600 : template<Executor Ex, class H1>
601 : [[nodiscard]] auto
602 2801 : run_async(Ex ex, std::stop_token st, H1 h1)
603 : {
604 2801 : auto* mr = ex.context().get_frame_allocator();
605 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
606 2801 : std::move(ex),
607 2801 : std::move(st),
608 2801 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
609 5602 : mr);
610 : }
611 :
612 : /** Asynchronously launch a lazy task with stop token and separate handlers.
613 :
614 : The stop token is propagated to the task for cooperative cancellation.
615 : The handler `h1` is called on success, `h2` on failure.
616 :
617 : @param ex The executor to execute the task on.
618 : @param st The stop token for cooperative cancellation.
619 : @param h1 The handler to invoke with the result on success.
620 : @param h2 The handler to invoke with the exception on failure.
621 :
622 : @return A wrapper that accepts a `task<T>` for immediate execution.
623 :
624 : @see task
625 : @see executor
626 : */
627 : template<Executor Ex, class H1, class H2>
628 : [[nodiscard]] auto
629 9 : run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
630 : {
631 9 : auto* mr = ex.context().get_frame_allocator();
632 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
633 9 : std::move(ex),
634 9 : std::move(st),
635 9 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
636 18 : mr);
637 : }
638 :
639 : // Ex + memory_resource*
640 :
641 : /** Asynchronously launch a lazy task with custom memory resource.
642 :
643 : The memory resource is used for coroutine frame allocation. The caller
644 : is responsible for ensuring the memory resource outlives all tasks.
645 :
646 : @param ex The executor to execute the task on.
647 : @param mr The memory resource for frame allocation.
648 :
649 : @return A wrapper that accepts a `task<T>` for immediate execution.
650 :
651 : @see task
652 : @see executor
653 : */
654 : template<Executor Ex>
655 : [[nodiscard]] auto
656 : run_async(Ex ex, std::pmr::memory_resource* mr)
657 : {
658 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
659 : std::move(ex),
660 : std::stop_token{},
661 : detail::default_handler{},
662 : mr);
663 : }
664 :
665 : /** Asynchronously launch a lazy task with memory resource and handler.
666 :
667 : @param ex The executor to execute the task on.
668 : @param mr The memory resource for frame allocation.
669 : @param h1 The handler to invoke with the result (and optionally exception).
670 :
671 : @return A wrapper that accepts a `task<T>` for immediate execution.
672 :
673 : @see task
674 : @see executor
675 : */
676 : template<Executor Ex, class H1>
677 : [[nodiscard]] auto
678 : run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
679 : {
680 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
681 : std::move(ex),
682 : std::stop_token{},
683 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
684 : mr);
685 : }
686 :
687 : /** Asynchronously launch a lazy task with memory resource and handlers.
688 :
689 : @param ex The executor to execute the task on.
690 : @param mr The memory resource for frame allocation.
691 : @param h1 The handler to invoke with the result on success.
692 : @param h2 The handler to invoke with the exception on failure.
693 :
694 : @return A wrapper that accepts a `task<T>` for immediate execution.
695 :
696 : @see task
697 : @see executor
698 : */
699 : template<Executor Ex, class H1, class H2>
700 : [[nodiscard]] auto
701 : run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
702 : {
703 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
704 : std::move(ex),
705 : std::stop_token{},
706 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
707 : mr);
708 : }
709 :
710 : // Ex + stop_token + memory_resource*
711 :
712 : /** Asynchronously launch a lazy task with stop token and memory resource.
713 :
714 : @param ex The executor to execute the task on.
715 : @param st The stop token for cooperative cancellation.
716 : @param mr The memory resource for frame allocation.
717 :
718 : @return A wrapper that accepts a `task<T>` for immediate execution.
719 :
720 : @see task
721 : @see executor
722 : */
723 : template<Executor Ex>
724 : [[nodiscard]] auto
725 : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
726 : {
727 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
728 : std::move(ex),
729 : std::move(st),
730 : detail::default_handler{},
731 : mr);
732 : }
733 :
734 : /** Asynchronously launch a lazy task with stop token, memory resource, and handler.
735 :
736 : @param ex The executor to execute the task on.
737 : @param st The stop token for cooperative cancellation.
738 : @param mr The memory resource for frame allocation.
739 : @param h1 The handler to invoke with the result (and optionally exception).
740 :
741 : @return A wrapper that accepts a `task<T>` for immediate execution.
742 :
743 : @see task
744 : @see executor
745 : */
746 : template<Executor Ex, class H1>
747 : [[nodiscard]] auto
748 : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
749 : {
750 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
751 : std::move(ex),
752 : std::move(st),
753 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
754 : mr);
755 : }
756 :
757 : /** Asynchronously launch a lazy task with stop token, memory resource, and handlers.
758 :
759 : @param ex The executor to execute the task on.
760 : @param st The stop token for cooperative cancellation.
761 : @param mr The memory resource for frame allocation.
762 : @param h1 The handler to invoke with the result on success.
763 : @param h2 The handler to invoke with the exception on failure.
764 :
765 : @return A wrapper that accepts a `task<T>` for immediate execution.
766 :
767 : @see task
768 : @see executor
769 : */
770 : template<Executor Ex, class H1, class H2>
771 : [[nodiscard]] auto
772 : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
773 : {
774 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
775 : std::move(ex),
776 : std::move(st),
777 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
778 : mr);
779 : }
780 :
781 : // Ex + standard Allocator (value type)
782 :
783 : /** Asynchronously launch a lazy task with custom allocator.
784 :
785 : The allocator is wrapped in a frame_memory_resource and stored in the
786 : run_async_trampoline, ensuring it outlives all coroutine frames.
787 :
788 : @param ex The executor to execute the task on.
789 : @param alloc The allocator for frame allocation (copied and stored).
790 :
791 : @return A wrapper that accepts a `task<T>` for immediate execution.
792 :
793 : @see task
794 : @see executor
795 : */
796 : template<Executor Ex, detail::Allocator Alloc>
797 : [[nodiscard]] auto
798 : run_async(Ex ex, Alloc alloc)
799 : {
800 : return run_async_wrapper<Ex, detail::default_handler, Alloc>(
801 : std::move(ex),
802 : std::stop_token{},
803 : detail::default_handler{},
804 : std::move(alloc));
805 : }
806 :
807 : /** Asynchronously launch a lazy task with allocator and handler.
808 :
809 : @param ex The executor to execute the task on.
810 : @param alloc The allocator for frame allocation (copied and stored).
811 : @param h1 The handler to invoke with the result (and optionally exception).
812 :
813 : @return A wrapper that accepts a `task<T>` for immediate execution.
814 :
815 : @see task
816 : @see executor
817 : */
818 : template<Executor Ex, detail::Allocator Alloc, class H1>
819 : [[nodiscard]] auto
820 : run_async(Ex ex, Alloc alloc, H1 h1)
821 : {
822 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
823 : std::move(ex),
824 : std::stop_token{},
825 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
826 : std::move(alloc));
827 : }
828 :
829 : /** Asynchronously launch a lazy task with allocator and handlers.
830 :
831 : @param ex The executor to execute the task on.
832 : @param alloc The allocator for frame allocation (copied and stored).
833 : @param h1 The handler to invoke with the result on success.
834 : @param h2 The handler to invoke with the exception on failure.
835 :
836 : @return A wrapper that accepts a `task<T>` for immediate execution.
837 :
838 : @see task
839 : @see executor
840 : */
841 : template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
842 : [[nodiscard]] auto
843 : run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
844 : {
845 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
846 : std::move(ex),
847 : std::stop_token{},
848 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
849 : std::move(alloc));
850 : }
851 :
852 : // Ex + stop_token + standard Allocator
853 :
854 : /** Asynchronously launch a lazy task with stop token and allocator.
855 :
856 : @param ex The executor to execute the task on.
857 : @param st The stop token for cooperative cancellation.
858 : @param alloc The allocator for frame allocation (copied and stored).
859 :
860 : @return A wrapper that accepts a `task<T>` for immediate execution.
861 :
862 : @see task
863 : @see executor
864 : */
865 : template<Executor Ex, detail::Allocator Alloc>
866 : [[nodiscard]] auto
867 : run_async(Ex ex, std::stop_token st, Alloc alloc)
868 : {
869 : return run_async_wrapper<Ex, detail::default_handler, Alloc>(
870 : std::move(ex),
871 : std::move(st),
872 : detail::default_handler{},
873 : std::move(alloc));
874 : }
875 :
876 : /** Asynchronously launch a lazy task with stop token, allocator, and handler.
877 :
878 : @param ex The executor to execute the task on.
879 : @param st The stop token for cooperative cancellation.
880 : @param alloc The allocator for frame allocation (copied and stored).
881 : @param h1 The handler to invoke with the result (and optionally exception).
882 :
883 : @return A wrapper that accepts a `task<T>` for immediate execution.
884 :
885 : @see task
886 : @see executor
887 : */
888 : template<Executor Ex, detail::Allocator Alloc, class H1>
889 : [[nodiscard]] auto
890 : run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
891 : {
892 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
893 : std::move(ex),
894 : std::move(st),
895 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
896 : std::move(alloc));
897 : }
898 :
899 : /** Asynchronously launch a lazy task with stop token, allocator, and handlers.
900 :
901 : @param ex The executor to execute the task on.
902 : @param st The stop token for cooperative cancellation.
903 : @param alloc The allocator for frame allocation (copied and stored).
904 : @param h1 The handler to invoke with the result on success.
905 : @param h2 The handler to invoke with the exception on failure.
906 :
907 : @return A wrapper that accepts a `task<T>` for immediate execution.
908 :
909 : @see task
910 : @see executor
911 : */
912 : template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
913 : [[nodiscard]] auto
914 : run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
915 : {
916 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
917 : std::move(ex),
918 : std::move(st),
919 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
920 : std::move(alloc));
921 : }
922 :
923 : } // namespace capy
924 : } // namespace boost
925 :
926 : #endif
|