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