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