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