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