TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/capy
9 : //
10 :
11 : #ifndef BOOST_CAPY_RUN_ASYNC_HPP
12 : #define BOOST_CAPY_RUN_ASYNC_HPP
13 :
14 : #include <boost/capy/detail/config.hpp>
15 : #include <boost/capy/detail/run.hpp>
16 : #include <boost/capy/detail/run_callbacks.hpp>
17 : #include <boost/capy/concept/executor.hpp>
18 : #include <boost/capy/concept/io_runnable.hpp>
19 : #include <boost/capy/ex/execution_context.hpp>
20 : #include <boost/capy/ex/frame_allocator.hpp>
21 : #include <boost/capy/ex/io_env.hpp>
22 : #include <boost/capy/ex/recycling_memory_resource.hpp>
23 : #include <boost/capy/ex/work_guard.hpp>
24 :
25 : #include <algorithm>
26 : #include <coroutine>
27 : #include <cstring>
28 : #include <exception>
29 : #include <memory_resource>
30 : #include <new>
31 : #include <stop_token>
32 : #include <type_traits>
33 :
34 : namespace boost {
35 : namespace capy {
36 : namespace detail {
37 :
38 : /** Match types usable as `run_async` completion handlers.
39 :
40 : Excludes the types meaningful to the other `run_async` parameters.
41 : A stop token, memory resource pointer, or allocator argument
42 : therefore selects its dedicated overload by conversion. It does not
43 : deduce as an exact-match handler.
44 : */
45 : template<class H>
46 : concept RunAsyncHandler =
47 : !std::is_convertible_v<H, std::pmr::memory_resource*> &&
48 : !std::is_convertible_v<H, std::stop_token> &&
49 : !Allocator<H>;
50 :
51 : /// Function pointer type for type-erased frame deallocation.
52 : using dealloc_fn = void(*)(void*, std::size_t);
53 :
54 : /// Type-erased deallocator implementation for trampoline frames.
55 : template<class Alloc>
56 HIT 3 : void dealloc_impl(void* raw, std::size_t total)
57 : {
58 : static_assert(std::is_same_v<typename Alloc::value_type, std::byte>);
59 3 : auto* a = std::launder(reinterpret_cast<Alloc*>(
60 3 : static_cast<char*>(raw) + total - sizeof(Alloc)));
61 3 : Alloc ba(std::move(*a));
62 1 : a->~Alloc();
63 1 : ba.deallocate(static_cast<std::byte*>(raw), total);
64 3 : }
65 :
66 : /// Awaiter to access the promise from within the coroutine.
67 : template<class Promise>
68 : struct get_promise_awaiter
69 : {
70 : Promise* p_ = nullptr;
71 :
72 1808 : bool await_ready() const noexcept { return false; }
73 :
74 1808 : bool await_suspend(std::coroutine_handle<Promise> h) noexcept
75 : {
76 1808 : p_ = &h.promise();
77 1808 : return false;
78 : }
79 :
80 1808 : Promise& await_resume() const noexcept
81 : {
82 1808 : return *p_;
83 : }
84 : };
85 :
86 : /** Internal run_async_trampoline coroutine for run_async.
87 :
88 : The run_async_trampoline is allocated BEFORE the task (via C++17 postfix evaluation
89 : order) and serves as the task's continuation. When the task final_suspends,
90 : control returns to the run_async_trampoline which then invokes the appropriate handler.
91 :
92 : For value-type allocators, the run_async_trampoline stores a frame_memory_resource
93 : that wraps the allocator. For memory_resource*, it stores the pointer directly.
94 :
95 : @tparam Ex The executor type.
96 : @tparam Handlers The handler type (default_handler or handler_pair).
97 : @tparam Alloc The allocator type (value type or memory_resource*).
98 : */
99 : template<class Ex, class Handlers, class Alloc>
100 : struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE run_async_trampoline
101 : {
102 : using invoke_fn = void(*)(void*, Handlers&);
103 :
104 : struct promise_type
105 : {
106 : work_guard<Ex> wg_;
107 : Handlers handlers_;
108 : frame_memory_resource<Alloc> resource_;
109 : io_env env_;
110 : invoke_fn invoke_ = nullptr;
111 : void* task_promise_ = nullptr;
112 : // task_h_: raw handle for frame_guard cleanup in make_trampoline.
113 : // task_cont_: continuation wrapping the same handle for executor dispatch.
114 : // Both must reference the same coroutine and be kept in sync.
115 : std::coroutine_handle<> task_h_;
116 : continuation task_cont_;
117 :
118 3 : promise_type(Ex& ex, Handlers& h, Alloc& a) noexcept
119 3 : : wg_(std::move(ex))
120 3 : , handlers_(std::move(h))
121 3 : , resource_(std::move(a))
122 : {
123 3 : }
124 :
125 3 : static void* operator new(
126 : std::size_t size, Ex const&, Handlers const&, Alloc a)
127 : {
128 : using byte_alloc = typename std::allocator_traits<Alloc>
129 : ::template rebind_alloc<std::byte>;
130 :
131 3 : constexpr auto footer_align =
132 : (std::max)(alignof(dealloc_fn), alignof(Alloc));
133 3 : auto padded = (size + footer_align - 1) & ~(footer_align - 1);
134 3 : auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
135 :
136 1 : byte_alloc ba(std::move(a));
137 3 : void* raw = ba.allocate(total);
138 :
139 3 : auto* fn_loc = reinterpret_cast<dealloc_fn*>(
140 : static_cast<char*>(raw) + padded);
141 3 : *fn_loc = &dealloc_impl<byte_alloc>;
142 :
143 3 : new (fn_loc + 1) byte_alloc(std::move(ba));
144 :
145 5 : return raw;
146 : }
147 :
148 3 : static void operator delete(void* ptr, std::size_t size)
149 : {
150 3 : constexpr auto footer_align =
151 : (std::max)(alignof(dealloc_fn), alignof(Alloc));
152 3 : auto padded = (size + footer_align - 1) & ~(footer_align - 1);
153 3 : auto total = padded + sizeof(dealloc_fn) + sizeof(Alloc);
154 :
155 3 : auto* fn = reinterpret_cast<dealloc_fn*>(
156 : static_cast<char*>(ptr) + padded);
157 3 : (*fn)(ptr, total);
158 3 : }
159 :
160 6 : std::pmr::memory_resource* get_resource() noexcept
161 : {
162 6 : return &resource_;
163 : }
164 :
165 3 : run_async_trampoline get_return_object() noexcept
166 : {
167 : return run_async_trampoline{
168 3 : std::coroutine_handle<promise_type>::from_promise(*this)};
169 : }
170 :
171 3 : std::suspend_always initial_suspend() noexcept
172 : {
173 3 : return {};
174 : }
175 :
176 3 : std::suspend_never final_suspend() noexcept
177 : {
178 3 : return {};
179 : }
180 :
181 3 : void return_void() noexcept
182 : {
183 3 : }
184 :
185 : // An exception reaches here only by escaping a handler: a handler
186 : // that threw, or the default handler rethrowing an otherwise
187 : // unhandled task exception. Cancellation is filtered out earlier
188 : // by default_handler, so this is always a genuine error with no
189 : // owner to receive it: fail fast.
190 : void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE
191 : };
192 :
193 : std::coroutine_handle<promise_type> h_;
194 :
195 : template<IoRunnable Task>
196 3 : static void invoke_impl(void* p, Handlers& h)
197 : {
198 : using R = decltype(std::declval<Task&>().await_resume());
199 3 : auto& promise = *static_cast<typename Task::promise_type*>(p);
200 3 : if(promise.exception())
201 1 : h(promise.exception());
202 : else if constexpr(std::is_void_v<R>)
203 1 : h();
204 : else
205 1 : h(std::move(promise.result()));
206 3 : }
207 : };
208 :
209 : /** Specialization for memory_resource* - stores pointer directly.
210 :
211 : This avoids double indirection when the user passes a memory_resource*.
212 : */
213 : template<class Ex, class Handlers>
214 : struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE
215 : run_async_trampoline<Ex, Handlers, std::pmr::memory_resource*>
216 : {
217 : using invoke_fn = void(*)(void*, Handlers&);
218 :
219 : struct promise_type
220 : {
221 : work_guard<Ex> wg_;
222 : Handlers handlers_;
223 : std::pmr::memory_resource* mr_;
224 : io_env env_;
225 : invoke_fn invoke_ = nullptr;
226 : void* task_promise_ = nullptr;
227 : // task_h_: raw handle for frame_guard cleanup in make_trampoline.
228 : // task_cont_: continuation wrapping the same handle for executor dispatch.
229 : // Both must reference the same coroutine and be kept in sync.
230 : std::coroutine_handle<> task_h_;
231 : continuation task_cont_;
232 :
233 1938 : promise_type(
234 : Ex& ex, Handlers& h, std::pmr::memory_resource* mr) noexcept
235 1938 : : wg_(std::move(ex))
236 1938 : , handlers_(std::move(h))
237 1938 : , mr_(mr)
238 : {
239 1938 : }
240 :
241 1938 : static void* operator new(
242 : std::size_t size, Ex const&, Handlers const&,
243 : std::pmr::memory_resource* mr)
244 : {
245 1938 : auto total = size + sizeof(mr);
246 1938 : void* raw = mr->allocate(total, alignof(std::max_align_t));
247 1938 : std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
248 1938 : return raw;
249 : }
250 :
251 1938 : static void operator delete(void* ptr, std::size_t size)
252 : {
253 : std::pmr::memory_resource* mr;
254 1938 : std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
255 1938 : auto total = size + sizeof(mr);
256 1938 : mr->deallocate(ptr, total, alignof(std::max_align_t));
257 1938 : }
258 :
259 3876 : std::pmr::memory_resource* get_resource() noexcept
260 : {
261 3876 : return mr_;
262 : }
263 :
264 1938 : run_async_trampoline get_return_object() noexcept
265 : {
266 : return run_async_trampoline{
267 1938 : std::coroutine_handle<promise_type>::from_promise(*this)};
268 : }
269 :
270 1938 : std::suspend_always initial_suspend() noexcept
271 : {
272 1938 : return {};
273 : }
274 :
275 1805 : std::suspend_never final_suspend() noexcept
276 : {
277 1805 : return {};
278 : }
279 :
280 1805 : void return_void() noexcept
281 : {
282 1805 : }
283 :
284 : // See primary template: an escaping handler exception is fatal.
285 : void unhandled_exception() noexcept { std::terminate(); } // LCOV_EXCL_LINE
286 : };
287 :
288 : std::coroutine_handle<promise_type> h_;
289 :
290 : template<IoRunnable Task>
291 1805 : static void invoke_impl(void* p, Handlers& h)
292 : {
293 : using R = decltype(std::declval<Task&>().await_resume());
294 1805 : auto& promise = *static_cast<typename Task::promise_type*>(p);
295 1805 : if(promise.exception())
296 373 : h(promise.exception());
297 : else if constexpr(std::is_void_v<R>)
298 1154 : h();
299 : else
300 278 : h(std::move(promise.result()));
301 1805 : }
302 : };
303 :
304 : /// Coroutine body for run_async_trampoline - invokes handlers then destroys task.
305 : template<class Ex, class Handlers, class Alloc>
306 : run_async_trampoline<Ex, Handlers, Alloc>
307 1941 : make_trampoline(Ex, Handlers, Alloc)
308 : {
309 : // promise_type ctor steals the parameters
310 : auto& p = co_await get_promise_awaiter<
311 : typename run_async_trampoline<Ex, Handlers, Alloc>::promise_type>{};
312 :
313 : // Guard ensures the task frame is destroyed even when invoke_
314 : // throws (e.g. default_handler rethrows an unhandled exception).
315 : struct frame_guard
316 : {
317 : std::coroutine_handle<>& h;
318 1808 : ~frame_guard() { h.destroy(); }
319 : } guard{p.task_h_};
320 :
321 : p.invoke_(p.task_promise_, p.handlers_);
322 3886 : }
323 :
324 : } // namespace detail
325 :
326 : /** Installs the frame allocator, then starts the task on the executor when called once.
327 :
328 : This wrapper holds the run_async_trampoline coroutine, executor, stop token,
329 : and handlers. The run_async_trampoline is allocated when the wrapper is constructed
330 : (before the task due to C++17 postfix evaluation order).
331 :
332 : The rvalue ref-qualifier on `operator()` ensures the wrapper can only
333 : be used as a temporary, preventing misuse that would violate LIFO ordering.
334 :
335 : @tparam Ex The executor type satisfying the `Executor` concept.
336 : @tparam Handlers The handler type (default_handler or handler_pair).
337 : @tparam Alloc The allocator type (value type or memory_resource*).
338 :
339 : @par Thread Safety
340 : The wrapper itself should only be used from one thread. The handlers
341 : may be invoked from any thread where the executor schedules work.
342 :
343 : @warning **Always construct the task as the direct argument of the
344 : two-call expression `run_async(ex)(task)`.** The wrapper's constructor
345 : installs the frame allocator in thread-local storage. The task's
346 : `operator new` reads that thread-local state. Splitting the two calls
347 : apart in any of the following ways allocates the task's coroutine
348 : frame under the wrong allocator. Each does so silently, with no
349 : compile error.
350 : @li *Stored wrapper.* Storing the wrapper itself
351 : (`auto w = run_async(ex);`) compiles fine. C++17 guaranteed copy
352 : elision constructs `w` directly from the prvalue. The deleted
353 : copy/move constructors are never considered. What the rvalue
354 : ref-qualifier on `operator()` rejects is calling through that
355 : stored lvalue: `w(my_task())` does not compile, and
356 : `std::move(w)(my_task())` is required instead. The silent
357 : variant is storing the *task*
358 : (`auto t = my_task(); run_async(ex)(std::move(t));`): `t`'s frame
359 : is allocated before `run_async(ex)` ever runs.
360 : @li *Preconstructed task.* Passing an already-constructed task object
361 : has the same effect as the stored-wrapper case. So does passing a
362 : moved-from local, or a task returned from an earlier statement.
363 : The frame exists before the allocator is installed.
364 : @li *Wrapper function.* Forwarding the task through a helper that
365 : itself performs the two-call pattern constructs the task as an
366 : argument to the helper. It is therefore constructed before the
367 : helper's body runs, and so before `run_async` runs. An example is
368 : `submit(ex, my_task())`, where `submit` calls
369 : `run_async(ex)(std::forward<Task>(t))` internally.
370 :
371 : See the Frame Allocators guide
372 : (`doc/modules/ROOT/pages/4.coroutines/4g.allocators.adoc`) for the full
373 : C++17-evaluation-order rationale behind this constraint.
374 :
375 : @par Example
376 : @code
377 : // Correct usage - wrapper is temporary, task is the direct argument
378 : run_async(ex)(my_task());
379 :
380 : // Compiles - copy elision constructs w directly from the prvalue
381 : auto w = run_async(ex);
382 : w(my_task()); // Compile error: operator() requires rvalue
383 : std::move(w)(my_task()); // Compiles: w is now an rvalue
384 :
385 : // Compiles, but WRONG - task frame allocated before run_async runs
386 : auto t = my_task();
387 : run_async(ex)(std::move(t));
388 : @endcode
389 :
390 : @see run_async
391 : */
392 : template<Executor Ex, class Handlers, class Alloc>
393 : class [[nodiscard]] run_async_wrapper
394 : {
395 : detail::run_async_trampoline<Ex, Handlers, Alloc> tr_;
396 : std::stop_token st_;
397 : std::pmr::memory_resource* saved_tls_;
398 :
399 : public:
400 : /** Construct the wrapper and install the frame allocator.
401 :
402 : Builds the trampoline and saves the current thread-local frame
403 : allocator. Then installs the trampoline's resource as the new
404 : thread-local allocator. The task frame, evaluated as the argument
405 : to @ref operator(), is therefore allocated from that resource.
406 :
407 : @param ex The executor on which the task runs.
408 : @param st The stop token for cooperative cancellation.
409 : @param h The completion handlers.
410 : @param a The allocator for frame allocation.
411 :
412 : @note When `Alloc` is not `std::pmr::memory_resource*` it must be
413 : nothrow move constructible (enforced by a `static_assert`), which
414 : is what allows this constructor to be `noexcept`.
415 : */
416 1941 : run_async_wrapper(
417 : Ex ex,
418 : std::stop_token st,
419 : Handlers h,
420 : Alloc a) noexcept
421 1942 : : tr_(detail::make_trampoline<Ex, Handlers, Alloc>(
422 1944 : std::move(ex), std::move(h), std::move(a)))
423 1941 : , st_(std::move(st))
424 1941 : , saved_tls_(get_current_frame_allocator())
425 : {
426 : if constexpr (!std::is_same_v<Alloc, std::pmr::memory_resource*>)
427 : {
428 : static_assert(
429 : std::is_nothrow_move_constructible_v<Alloc>,
430 : "Allocator must be nothrow move constructible");
431 : }
432 : // Set TLS before task argument is evaluated
433 1941 : set_current_frame_allocator(tr_.h_.promise().get_resource());
434 1941 : }
435 :
436 : /** Restore the previously installed frame allocator.
437 :
438 : Resets the thread-local frame allocator to the value saved at
439 : construction. A stale pointer to the trampoline's resource
440 : therefore does not outlive the execution context that owns it.
441 : */
442 1941 : ~run_async_wrapper()
443 : {
444 1941 : set_current_frame_allocator(saved_tls_);
445 1941 : }
446 :
447 : // Non-copyable, non-movable (must be used immediately)
448 :
449 : /** Copy construction is disabled; the wrapper must be used immediately.
450 :
451 : @param other The wrapper that would be copied.
452 : */
453 : run_async_wrapper(run_async_wrapper const& other) = delete;
454 :
455 : /** Move construction is disabled; the wrapper must be used immediately.
456 :
457 : @param other The wrapper that would be moved from.
458 : */
459 : run_async_wrapper(run_async_wrapper&& other) = delete;
460 :
461 : /** Copy assignment is disabled; the wrapper must be used immediately.
462 :
463 : @param other The wrapper that would be assigned from.
464 :
465 : @return A reference to `*this`.
466 : */
467 : run_async_wrapper& operator=(run_async_wrapper const& other) = delete;
468 :
469 : /** Move assignment is disabled; the wrapper must be used immediately.
470 :
471 : @param other The wrapper that would be moved from.
472 :
473 : @return A reference to `*this`.
474 : */
475 : run_async_wrapper& operator=(run_async_wrapper&& other) = delete;
476 :
477 : /** Start the task for execution.
478 :
479 : This operator accepts a task and starts it on the executor.
480 : The rvalue ref-qualifier ensures the wrapper is consumed, enforcing
481 : correct LIFO destruction order.
482 :
483 : The `io_env` constructed for the task is owned by the trampoline
484 : coroutine and is guaranteed to outlive the task and all awaitables
485 : in its chain. Awaitables may store `io_env const*` without concern
486 : for dangling references.
487 :
488 : @tparam Task The IoRunnable type.
489 :
490 : @param t The task to execute. Ownership is transferred to the
491 : run_async_trampoline which destroys it after completion.
492 : */
493 : template<IoRunnable Task>
494 1941 : void operator()(Task t) &&
495 : {
496 1941 : auto task_h = t.handle();
497 1941 : auto& task_promise = task_h.promise();
498 1941 : t.release();
499 :
500 1941 : auto& p = tr_.h_.promise();
501 :
502 : // Inject Task-specific invoke function
503 1941 : p.invoke_ = detail::run_async_trampoline<Ex, Handlers, Alloc>::template invoke_impl<Task>;
504 1941 : p.task_promise_ = &task_promise;
505 1941 : p.task_h_ = task_h;
506 :
507 : // Setup task's continuation to return to run_async_trampoline
508 1941 : task_promise.set_continuation(tr_.h_);
509 3882 : p.env_ = {p.wg_.executor(), st_, p.get_resource()};
510 1941 : task_promise.set_environment(&p.env_);
511 :
512 : // Start task through executor.
513 : // safe_resume is not needed here: TLS is already saved in the
514 : // constructor (saved_tls_) and restored in the destructor.
515 1941 : p.task_cont_.h = task_h;
516 1941 : p.wg_.executor().dispatch(p.task_cont_).resume();
517 3882 : }
518 : };
519 :
520 : // Executor only (uses default recycling allocator)
521 :
522 : /** Bind an executor to produce a launcher. Invoke the launcher with a task to start it.
523 :
524 : Use this to start execution of a `task<T>` that was created lazily.
525 : The returned wrapper must be immediately invoked with the task;
526 : storing the wrapper and calling it later violates LIFO ordering.
527 :
528 : Uses the default recycling frame allocator for coroutine frames.
529 : With no handlers, the result is discarded. An unhandled exception
530 : thrown by the task calls `std::terminate`. To catch it instead, pass
531 : an error handler that receives it as an `exception_ptr`, or `co_await`
532 : the work inside a coroutine.
533 :
534 : Construct the task as the direct argument of the two-call expression
535 : `run_async(ex)(task)`.
536 :
537 : @par Thread Safety
538 : The wrapper itself should only be used from one thread.
539 :
540 : @par Example
541 : @code
542 : run_async(ioc.get_executor())(my_task());
543 : @endcode
544 :
545 : @param ex The executor to execute the task on.
546 :
547 : @return A wrapper that accepts a `task<T>` for immediate execution.
548 :
549 : @see task
550 : @see Executor
551 : @see run_async_wrapper
552 : */
553 : template<Executor Ex>
554 : [[nodiscard]] auto
555 209 : run_async(Ex ex)
556 : {
557 209 : auto* mr = ex.context().get_frame_allocator();
558 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
559 209 : std::move(ex),
560 418 : std::stop_token{},
561 : detail::default_handler{},
562 209 : mr);
563 : }
564 :
565 : /** Bind an executor and a result handler to produce a launcher. Invoke the launcher with a task to start it.
566 :
567 : The handler `h1` is called with the task's result on success. If `h1`
568 : is also invocable with `std::exception_ptr`, it handles exceptions too.
569 : Otherwise, an unhandled exception calls `std::terminate`.
570 :
571 : Construct the task as the direct argument of the two-call expression
572 : `run_async(ex)(task)`.
573 :
574 : @par Thread Safety
575 : The wrapper itself should only be used from one thread. The handlers
576 : may be invoked from any thread where the executor schedules work.
577 :
578 : @par Example
579 : @code
580 : // Handler for result only (exceptions rethrown)
581 : run_async(ex, [](int result) {
582 : std::cout << "Got: " << result << "\n";
583 : })(compute_value());
584 :
585 : // Overloaded handler for both result and exception
586 : run_async(ex, overloaded{
587 : [](int result) { std::cout << "Got: " << result << "\n"; },
588 : [](std::exception_ptr) { std::cout << "Failed\n"; }
589 : })(compute_value());
590 : @endcode
591 :
592 : @param ex The executor to execute the task on.
593 : @param h1 The handler to invoke with the result (and optionally exception).
594 :
595 : @return A wrapper that accepts a `task<T>` for immediate execution.
596 :
597 : @see task
598 : @see Executor
599 : @see run_async_wrapper
600 : */
601 : template<Executor Ex, class H1>
602 : requires detail::RunAsyncHandler<H1>
603 : [[nodiscard]] auto
604 109 : run_async(Ex ex, H1 h1)
605 : {
606 109 : auto* mr = ex.context().get_frame_allocator();
607 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
608 109 : std::move(ex),
609 115 : std::stop_token{},
610 103 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
611 212 : mr);
612 : }
613 :
614 : /** Bind an executor and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
615 :
616 : The handler `h1` is called with the task's result on success.
617 : The handler `h2` is called with the exception_ptr on failure.
618 :
619 : Construct the task as the direct argument of the two-call expression
620 : `run_async(ex)(task)`.
621 :
622 : @par Thread Safety
623 : The wrapper itself should only be used from one thread. The handlers
624 : may be invoked from any thread where the executor schedules work.
625 :
626 : @par Example
627 : @code
628 : run_async(ex,
629 : [](int result) { std::cout << "Got: " << result << "\n"; },
630 : [](std::exception_ptr ep) {
631 : try { std::rethrow_exception(ep); }
632 : catch (std::exception const& e) {
633 : std::cout << "Error: " << e.what() << "\n";
634 : }
635 : }
636 : )(compute_value());
637 : @endcode
638 :
639 : @param ex The executor to execute the task on.
640 : @param h1 The handler to invoke with the result on success.
641 : @param h2 The handler to invoke with the exception on failure.
642 :
643 : @return A wrapper that accepts a `task<T>` for immediate execution.
644 :
645 : @see task
646 : @see Executor
647 : @see run_async_wrapper
648 : */
649 : template<Executor Ex, class H1, class H2>
650 : requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>)
651 : [[nodiscard]] auto
652 95 : run_async(Ex ex, H1 h1, H2 h2)
653 : {
654 95 : auto* mr = ex.context().get_frame_allocator();
655 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
656 95 : std::move(ex),
657 98 : std::stop_token{},
658 92 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
659 187 : mr);
660 1 : }
661 :
662 : // Ex + stop_token
663 :
664 : /** Bind an executor and a stop token to produce a launcher. Invoke the launcher with a task to start it.
665 :
666 : The stop token is propagated to the task, enabling cooperative
667 : cancellation. With no handlers, the result is discarded and an
668 : unhandled exception calls `std::terminate`.
669 :
670 : Construct the task as the direct argument of the two-call expression
671 : `run_async(ex)(task)`.
672 :
673 : @par Thread Safety
674 : The wrapper itself should only be used from one thread.
675 :
676 : @par Example
677 : @code
678 : std::stop_source source;
679 : run_async(ex, source.get_token())(cancellable_task());
680 : // Later: source.request_stop();
681 : @endcode
682 :
683 : @param ex The executor to execute the task on.
684 : @param st The stop token for cooperative cancellation.
685 :
686 : @return A wrapper that accepts a `task<T>` for immediate execution.
687 :
688 : @see task
689 : @see Executor
690 : @see run_async_wrapper
691 : */
692 : template<Executor Ex>
693 : [[nodiscard]] auto
694 371 : run_async(Ex ex, std::stop_token st)
695 : {
696 371 : auto* mr = ex.context().get_frame_allocator();
697 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
698 371 : std::move(ex),
699 371 : std::move(st),
700 : detail::default_handler{},
701 742 : mr);
702 : }
703 :
704 : /** Bind an executor, a stop token, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
705 :
706 : The stop token is propagated to the task for cooperative cancellation.
707 : The handler `h1` is called with the result on success, and optionally
708 : with exception_ptr if it accepts that type.
709 :
710 : Construct the task as the direct argument of the two-call expression
711 : `run_async(ex)(task)`.
712 :
713 : @par Thread Safety
714 : The wrapper itself should only be used from one thread. The handlers
715 : may be invoked from any thread where the executor schedules work.
716 :
717 : @param ex The executor to execute the task on.
718 : @param st The stop token for cooperative cancellation.
719 : @param h1 The handler to invoke with the result (and optionally exception).
720 :
721 : @return A wrapper that accepts a `task<T>` for immediate execution.
722 :
723 : @see task
724 : @see Executor
725 : @see run_async_wrapper
726 : */
727 : template<Executor Ex, class H1>
728 : requires detail::RunAsyncHandler<H1>
729 : [[nodiscard]] auto
730 1123 : run_async(Ex ex, std::stop_token st, H1 h1)
731 : {
732 1123 : auto* mr = ex.context().get_frame_allocator();
733 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
734 1123 : std::move(ex),
735 1123 : std::move(st),
736 1123 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
737 2246 : mr);
738 : }
739 :
740 : /** Bind an executor, a stop token, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
741 :
742 : The stop token is propagated to the task for cooperative cancellation.
743 : The handler `h1` is called on success, `h2` on failure.
744 :
745 : Construct the task as the direct argument of the two-call expression
746 : `run_async(ex)(task)`.
747 :
748 : @par Thread Safety
749 : The wrapper itself should only be used from one thread. The handlers
750 : may be invoked from any thread where the executor schedules work.
751 :
752 : @param ex The executor to execute the task on.
753 : @param st The stop token for cooperative cancellation.
754 : @param h1 The handler to invoke with the result on success.
755 : @param h2 The handler to invoke with the exception on failure.
756 :
757 : @return A wrapper that accepts a `task<T>` for immediate execution.
758 :
759 : @see task
760 : @see Executor
761 : @see run_async_wrapper
762 : */
763 : template<Executor Ex, class H1, class H2>
764 : requires (detail::RunAsyncHandler<H1> && detail::RunAsyncHandler<H2>)
765 : [[nodiscard]] auto
766 12 : run_async(Ex ex, std::stop_token st, H1 h1, H2 h2)
767 : {
768 12 : auto* mr = ex.context().get_frame_allocator();
769 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
770 12 : std::move(ex),
771 12 : std::move(st),
772 12 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
773 24 : mr);
774 : }
775 :
776 : // Ex + memory_resource*
777 :
778 : /** Bind an executor and a memory resource to produce a launcher. Invoke the launcher with a task to start it.
779 :
780 : The memory resource is used for coroutine frame allocation.
781 :
782 : Construct the task as the direct argument of the two-call expression
783 : `run_async(ex)(task)`.
784 :
785 : @par Thread Safety
786 : The wrapper itself should only be used from one thread.
787 :
788 : @pre `mr` outlives every task started through the returned wrapper.
789 :
790 : @param ex The executor to execute the task on.
791 : @param mr The memory resource for frame allocation.
792 :
793 : @return A wrapper that accepts a `task<T>` for immediate execution.
794 :
795 : @see task
796 : @see Executor
797 : @see run_async_wrapper
798 : */
799 : template<Executor Ex>
800 : [[nodiscard]] auto
801 16 : run_async(Ex ex, std::pmr::memory_resource* mr)
802 : {
803 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
804 16 : std::move(ex),
805 32 : std::stop_token{},
806 : detail::default_handler{},
807 16 : mr);
808 : }
809 :
810 : /** Bind an executor, a memory resource, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
811 :
812 : Construct the task as the direct argument of the two-call expression
813 : `run_async(ex)(task)`.
814 :
815 : @par Thread Safety
816 : The wrapper itself should only be used from one thread. The handlers
817 : may be invoked from any thread where the executor schedules work.
818 :
819 : @pre `mr` outlives every task started through the returned wrapper.
820 :
821 : @param ex The executor to execute the task on.
822 : @param mr The memory resource for frame allocation.
823 : @param h1 The handler to invoke with the result (and optionally exception).
824 :
825 : @return A wrapper that accepts a `task<T>` for immediate execution.
826 :
827 : @see task
828 : @see Executor
829 : @see run_async_wrapper
830 : */
831 : template<Executor Ex, class H1>
832 : [[nodiscard]] auto
833 1 : run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1)
834 : {
835 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
836 1 : std::move(ex),
837 1 : std::stop_token{},
838 1 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
839 2 : mr);
840 : }
841 :
842 : /** Bind an executor, a memory resource, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
843 :
844 : Construct the task as the direct argument of the two-call expression
845 : `run_async(ex)(task)`.
846 :
847 : @par Thread Safety
848 : The wrapper itself should only be used from one thread. The handlers
849 : may be invoked from any thread where the executor schedules work.
850 :
851 : @pre `mr` outlives every task started through the returned wrapper.
852 :
853 : @param ex The executor to execute the task on.
854 : @param mr The memory resource for frame allocation.
855 : @param h1 The handler to invoke with the result on success.
856 : @param h2 The handler to invoke with the exception on failure.
857 :
858 : @return A wrapper that accepts a `task<T>` for immediate execution.
859 :
860 : @see task
861 : @see Executor
862 : @see run_async_wrapper
863 : */
864 : template<Executor Ex, class H1, class H2>
865 : [[nodiscard]] auto
866 : run_async(Ex ex, std::pmr::memory_resource* mr, H1 h1, H2 h2)
867 : {
868 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
869 : std::move(ex),
870 : std::stop_token{},
871 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
872 : mr);
873 : }
874 :
875 : // Ex + stop_token + memory_resource*
876 :
877 : /** Bind an executor, a stop token, and a memory resource to produce a launcher. Invoke the launcher with a task to start it.
878 :
879 : Construct the task as the direct argument of the two-call expression
880 : `run_async(ex)(task)`.
881 :
882 : @par Thread Safety
883 : The wrapper itself should only be used from one thread.
884 :
885 : @pre `mr` outlives every task started through the returned wrapper.
886 :
887 : @param ex The executor to execute the task on.
888 : @param st The stop token for cooperative cancellation.
889 : @param mr The memory resource for frame allocation.
890 :
891 : @return A wrapper that accepts a `task<T>` for immediate execution.
892 :
893 : @see task
894 : @see Executor
895 : @see run_async_wrapper
896 : */
897 : template<Executor Ex>
898 : [[nodiscard]] auto
899 1 : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
900 : {
901 : return run_async_wrapper<Ex, detail::default_handler, std::pmr::memory_resource*>(
902 1 : std::move(ex),
903 1 : std::move(st),
904 : detail::default_handler{},
905 2 : mr);
906 : }
907 :
908 : /** Bind an executor, a stop token, a memory resource, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
909 :
910 : Construct the task as the direct argument of the two-call expression
911 : `run_async(ex)(task)`.
912 :
913 : @par Thread Safety
914 : The wrapper itself should only be used from one thread. The handlers
915 : may be invoked from any thread where the executor schedules work.
916 :
917 : @pre `mr` outlives every task started through the returned wrapper.
918 :
919 : @param ex The executor to execute the task on.
920 : @param st The stop token for cooperative cancellation.
921 : @param mr The memory resource for frame allocation.
922 : @param h1 The handler to invoke with the result (and optionally exception).
923 :
924 : @return A wrapper that accepts a `task<T>` for immediate execution.
925 :
926 : @see task
927 : @see Executor
928 : @see run_async_wrapper
929 : */
930 : template<Executor Ex, class H1>
931 : [[nodiscard]] auto
932 : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1)
933 : {
934 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, std::pmr::memory_resource*>(
935 : std::move(ex),
936 : std::move(st),
937 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
938 : mr);
939 : }
940 :
941 : /** Bind an executor, a stop token, a memory resource, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
942 :
943 : Construct the task as the direct argument of the two-call expression
944 : `run_async(ex)(task)`.
945 :
946 : @par Thread Safety
947 : The wrapper itself should only be used from one thread. The handlers
948 : may be invoked from any thread where the executor schedules work.
949 :
950 : @pre `mr` outlives every task started through the returned wrapper.
951 :
952 : @param ex The executor to execute the task on.
953 : @param st The stop token for cooperative cancellation.
954 : @param mr The memory resource for frame allocation.
955 : @param h1 The handler to invoke with the result on success.
956 : @param h2 The handler to invoke with the exception on failure.
957 :
958 : @return A wrapper that accepts a `task<T>` for immediate execution.
959 :
960 : @see task
961 : @see Executor
962 : @see run_async_wrapper
963 : */
964 : template<Executor Ex, class H1, class H2>
965 : [[nodiscard]] auto
966 1 : run_async(Ex ex, std::stop_token st, std::pmr::memory_resource* mr, H1 h1, H2 h2)
967 : {
968 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, std::pmr::memory_resource*>(
969 1 : std::move(ex),
970 1 : std::move(st),
971 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
972 2 : mr);
973 : }
974 :
975 : // Ex + standard Allocator (value type)
976 :
977 : /** Bind an executor and an allocator to produce a launcher. Invoke the launcher with a task to start it.
978 :
979 : The allocator is wrapped in a frame_memory_resource and stored in the
980 : run_async_trampoline, ensuring it outlives all coroutine frames.
981 :
982 : Construct the task as the direct argument of the two-call expression
983 : `run_async(ex)(task)`.
984 :
985 : @par Thread Safety
986 : The wrapper itself should only be used from one thread.
987 :
988 : @param ex The executor to execute the task on.
989 : @param alloc The allocator for frame allocation (copied and stored).
990 :
991 : @return A wrapper that accepts a `task<T>` for immediate execution.
992 :
993 : @see task
994 : @see Executor
995 : @see run_async_wrapper
996 : */
997 : template<Executor Ex, detail::Allocator Alloc>
998 : [[nodiscard]] auto
999 1 : run_async(Ex ex, Alloc alloc)
1000 : {
1001 : return run_async_wrapper<Ex, detail::default_handler, Alloc>(
1002 1 : std::move(ex),
1003 2 : std::stop_token{},
1004 : detail::default_handler{},
1005 2 : std::move(alloc));
1006 : }
1007 :
1008 : /** Bind an executor, an allocator, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
1009 :
1010 : Construct the task as the direct argument of the two-call expression
1011 : `run_async(ex)(task)`.
1012 :
1013 : @par Thread Safety
1014 : The wrapper itself should only be used from one thread. The handlers
1015 : may be invoked from any thread where the executor schedules work.
1016 :
1017 : @param ex The executor to execute the task on.
1018 : @param alloc The allocator for frame allocation (copied and stored).
1019 : @param h1 The handler to invoke with the result (and optionally exception).
1020 :
1021 : @return A wrapper that accepts a `task<T>` for immediate execution.
1022 :
1023 : @see task
1024 : @see Executor
1025 : @see run_async_wrapper
1026 : */
1027 : template<Executor Ex, detail::Allocator Alloc, class H1>
1028 : [[nodiscard]] auto
1029 1 : run_async(Ex ex, Alloc alloc, H1 h1)
1030 : {
1031 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
1032 1 : std::move(ex),
1033 1 : std::stop_token{},
1034 1 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
1035 4 : std::move(alloc));
1036 : }
1037 :
1038 : /** Bind an executor, an allocator, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
1039 :
1040 : Construct the task as the direct argument of the two-call expression
1041 : `run_async(ex)(task)`.
1042 :
1043 : @par Thread Safety
1044 : The wrapper itself should only be used from one thread. The handlers
1045 : may be invoked from any thread where the executor schedules work.
1046 :
1047 : @param ex The executor to execute the task on.
1048 : @param alloc The allocator for frame allocation (copied and stored).
1049 : @param h1 The handler to invoke with the result on success.
1050 : @param h2 The handler to invoke with the exception on failure.
1051 :
1052 : @return A wrapper that accepts a `task<T>` for immediate execution.
1053 :
1054 : @see task
1055 : @see Executor
1056 : @see run_async_wrapper
1057 : */
1058 : template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
1059 : [[nodiscard]] auto
1060 1 : run_async(Ex ex, Alloc alloc, H1 h1, H2 h2)
1061 : {
1062 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
1063 1 : std::move(ex),
1064 1 : std::stop_token{},
1065 1 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
1066 4 : std::move(alloc));
1067 : }
1068 :
1069 : // Ex + stop_token + standard Allocator
1070 :
1071 : /** Bind an executor, a stop token, and an allocator to produce a launcher. Invoke the launcher with a task to start it.
1072 :
1073 : Construct the task as the direct argument of the two-call expression
1074 : `run_async(ex)(task)`.
1075 :
1076 : @par Thread Safety
1077 : The wrapper itself should only be used from one thread.
1078 :
1079 : @param ex The executor to execute the task on.
1080 : @param st The stop token for cooperative cancellation.
1081 : @param alloc The allocator for frame allocation (copied and stored).
1082 :
1083 : @return A wrapper that accepts a `task<T>` for immediate execution.
1084 :
1085 : @see task
1086 : @see Executor
1087 : @see run_async_wrapper
1088 : */
1089 : template<Executor Ex, detail::Allocator Alloc>
1090 : [[nodiscard]] auto
1091 : run_async(Ex ex, std::stop_token st, Alloc alloc)
1092 : {
1093 : return run_async_wrapper<Ex, detail::default_handler, Alloc>(
1094 : std::move(ex),
1095 : std::move(st),
1096 : detail::default_handler{},
1097 : std::move(alloc));
1098 : }
1099 :
1100 : /** Bind an executor, a stop token, an allocator, and a result handler to produce a launcher. Invoke the launcher with a task to start it.
1101 :
1102 : Construct the task as the direct argument of the two-call expression
1103 : `run_async(ex)(task)`.
1104 :
1105 : @par Thread Safety
1106 : The wrapper itself should only be used from one thread. The handlers
1107 : may be invoked from any thread where the executor schedules work.
1108 :
1109 : @param ex The executor to execute the task on.
1110 : @param st The stop token for cooperative cancellation.
1111 : @param alloc The allocator for frame allocation (copied and stored).
1112 : @param h1 The handler to invoke with the result (and optionally exception).
1113 :
1114 : @return A wrapper that accepts a `task<T>` for immediate execution.
1115 :
1116 : @see task
1117 : @see Executor
1118 : @see run_async_wrapper
1119 : */
1120 : template<Executor Ex, detail::Allocator Alloc, class H1>
1121 : [[nodiscard]] auto
1122 : run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1)
1123 : {
1124 : return run_async_wrapper<Ex, detail::handler_pair<H1, detail::default_handler>, Alloc>(
1125 : std::move(ex),
1126 : std::move(st),
1127 : detail::handler_pair<H1, detail::default_handler>{std::move(h1)},
1128 : std::move(alloc));
1129 : }
1130 :
1131 : /** Bind an executor, a stop token, an allocator, and separate result and error handlers to produce a launcher. Invoke the launcher with a task to start it.
1132 :
1133 : Construct the task as the direct argument of the two-call expression
1134 : `run_async(ex)(task)`.
1135 :
1136 : @par Thread Safety
1137 : The wrapper itself should only be used from one thread. The handlers
1138 : may be invoked from any thread where the executor schedules work.
1139 :
1140 : @param ex The executor to execute the task on.
1141 : @param st The stop token for cooperative cancellation.
1142 : @param alloc The allocator for frame allocation (copied and stored).
1143 : @param h1 The handler to invoke with the result on success.
1144 : @param h2 The handler to invoke with the exception on failure.
1145 :
1146 : @return A wrapper that accepts a `task<T>` for immediate execution.
1147 :
1148 : @see task
1149 : @see Executor
1150 : @see run_async_wrapper
1151 : */
1152 : template<Executor Ex, detail::Allocator Alloc, class H1, class H2>
1153 : [[nodiscard]] auto
1154 : run_async(Ex ex, std::stop_token st, Alloc alloc, H1 h1, H2 h2)
1155 : {
1156 : return run_async_wrapper<Ex, detail::handler_pair<H1, H2>, Alloc>(
1157 : std::move(ex),
1158 : std::move(st),
1159 : detail::handler_pair<H1, H2>{std::move(h1), std::move(h2)},
1160 : std::move(alloc));
1161 : }
1162 :
1163 : } // namespace capy
1164 : } // namespace boost
1165 :
1166 : #endif
|