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