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