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