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