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