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