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