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