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