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