include/boost/capy/ex/run.hpp

99.5% Lines (186/187) 99.2% Functions (124/125)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_RUN_HPP
11 #define BOOST_CAPY_RUN_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/detail/await_suspend_helper.hpp>
15 #include <boost/capy/detail/run.hpp>
16 #include <boost/capy/concept/executor.hpp>
17 #include <boost/capy/concept/io_runnable.hpp>
18 #include <boost/capy/ex/executor_ref.hpp>
19 #include <coroutine>
20 #include <boost/capy/ex/frame_allocator.hpp>
21 #include <boost/capy/ex/io_env.hpp>
22
23 #include <memory_resource>
24 #include <stop_token>
25 #include <type_traits>
26 #include <utility>
27 #include <variant>
28
29 /*
30 Allocator Lifetime Strategy
31 ===========================
32
33 When using run() with a custom allocator:
34
35 co_await run(ex, alloc)(my_task());
36
37 The evaluation order is:
38 1. run(ex, alloc) creates a temporary wrapper
39 2. my_task() allocates its coroutine frame using TLS
40 3. operator() returns an awaitable
41 4. Wrapper temporary is DESTROYED
42 5. co_await suspends caller, resumes task
43 6. Task body executes (wrapper is already dead!)
44
45 Problem: The wrapper's frame_memory_resource dies before the task
46 body runs. When initial_suspend::await_resume() restores TLS from
47 the saved pointer, it would point to dead memory.
48
49 Solution: Store a COPY of the allocator in the awaitable (not just
50 the wrapper). The co_await mechanism extends the awaitable's lifetime
51 until the await completes. In await_suspend, we overwrite the promise's
52 saved frame_allocator pointer to point to the awaitable's resource.
53
54 This works because standard allocator copies are equivalent - memory
55 allocated with one copy can be deallocated with another copy. The
56 task's own frame uses the footer-stored pointer (safe), while nested
57 task creation uses TLS pointing to the awaitable's resource (also safe).
58 */
59
60 namespace boost::capy::detail {
61
62 //----------------------------------------------------------
63 //
64 // dispatch_trampoline - cross-executor dispatch
65 //
66 //----------------------------------------------------------
67
68 /** Minimal coroutine that dispatches through the caller's executor.
69
70 Sits between the inner task and the parent when executors
71 diverge. The inner task's `final_suspend` resumes this
72 trampoline via symmetric transfer. The trampoline's own
73 `final_suspend` dispatches the parent through the caller's
74 executor to restore the correct execution context.
75
76 The trampoline never touches the task's result.
77 */
78 struct dispatch_trampoline
79 {
80 struct promise_type
81 {
82 executor_ref caller_ex_;
83 std::coroutine_handle<> parent_;
84
85 9x dispatch_trampoline get_return_object() noexcept
86 {
87 return dispatch_trampoline{
88 9x std::coroutine_handle<promise_type>::from_promise(*this)};
89 }
90
91 9x std::suspend_always initial_suspend() noexcept { return {}; }
92
93 9x auto final_suspend() noexcept
94 {
95 struct awaiter
96 {
97 promise_type* p_;
98 9x bool await_ready() const noexcept { return false; }
99
100 9x auto await_suspend(
101 std::coroutine_handle<>) noexcept
102 {
103 9x return detail::symmetric_transfer(
104 18x p_->caller_ex_.dispatch(p_->parent_));
105 }
106
107 void await_resume() const noexcept {}
108 };
109 9x return awaiter{this};
110 }
111
112 9x void return_void() noexcept {}
113 void unhandled_exception() noexcept {}
114 };
115
116 std::coroutine_handle<promise_type> h_{nullptr};
117
118 9x dispatch_trampoline() noexcept = default;
119
120 27x ~dispatch_trampoline()
121 {
122 27x if(h_) h_.destroy();
123 27x }
124
125 dispatch_trampoline(dispatch_trampoline const&) = delete;
126 dispatch_trampoline& operator=(dispatch_trampoline const&) = delete;
127
128 9x dispatch_trampoline(dispatch_trampoline&& o) noexcept
129 9x : h_(std::exchange(o.h_, nullptr)) {}
130
131 9x dispatch_trampoline& operator=(dispatch_trampoline&& o) noexcept
132 {
133 9x if(this != &o)
134 {
135 9x if(h_) h_.destroy();
136 9x h_ = std::exchange(o.h_, nullptr);
137 }
138 9x return *this;
139 }
140
141 private:
142 9x explicit dispatch_trampoline(std::coroutine_handle<promise_type> h) noexcept
143 9x : h_(h) {}
144 };
145
146 9x inline dispatch_trampoline make_dispatch_trampoline()
147 {
148 co_return;
149 18x }
150
151 //----------------------------------------------------------
152 //
153 // run_awaitable_ex - with executor (executor switch)
154 //
155 //----------------------------------------------------------
156
157 /** Awaitable that binds an IoRunnable to a specific executor.
158
159 Stores the executor and inner task by value. When co_awaited, the
160 co_await expression's lifetime extension keeps both alive for the
161 duration of the operation.
162
163 A dispatch trampoline handles the executor switch on completion:
164 the inner task's `final_suspend` resumes the trampoline, which
165 dispatches back through the caller's executor.
166
167 The `io_env` is owned by this awaitable and is guaranteed to
168 outlive the inner task and all awaitables in its chain. Awaitables
169 may store `io_env const*` without concern for dangling references.
170
171 @tparam Task The IoRunnable type
172 @tparam Ex The executor type
173 @tparam InheritStopToken If true, inherit caller's stop token
174 @tparam Alloc The allocator type (void for no allocator)
175 */
176 template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
177 struct [[nodiscard]] run_awaitable_ex
178 {
179 Ex ex_;
180 frame_memory_resource<Alloc> resource_;
181 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
182 io_env env_;
183 dispatch_trampoline tr_;
184 Task inner_; // Last: destroyed first, while env_ is still valid
185
186 // void allocator, inherit stop token
187 3x run_awaitable_ex(Ex ex, Task inner)
188 requires (InheritStopToken && std::is_void_v<Alloc>)
189 3x : ex_(std::move(ex))
190 3x , inner_(std::move(inner))
191 {
192 3x }
193
194 // void allocator, explicit stop token
195 2x run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
196 requires (!InheritStopToken && std::is_void_v<Alloc>)
197 2x : ex_(std::move(ex))
198 2x , st_(std::move(st))
199 2x , inner_(std::move(inner))
200 {
201 2x }
202
203 // with allocator, inherit stop token (use template to avoid void parameter)
204 template<class A>
205 requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
206 2x run_awaitable_ex(Ex ex, A alloc, Task inner)
207 2x : ex_(std::move(ex))
208 2x , resource_(std::move(alloc))
209 2x , inner_(std::move(inner))
210 {
211 2x }
212
213 // with allocator, explicit stop token (use template to avoid void parameter)
214 template<class A>
215 requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
216 2x run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
217 2x : ex_(std::move(ex))
218 2x , resource_(std::move(alloc))
219 2x , st_(std::move(st))
220 2x , inner_(std::move(inner))
221 {
222 2x }
223
224 9x bool await_ready() const noexcept
225 {
226 9x return inner_.await_ready();
227 }
228
229 9x decltype(auto) await_resume()
230 {
231 9x return inner_.await_resume();
232 }
233
234 9x std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
235 {
236 9x tr_ = make_dispatch_trampoline();
237 9x tr_.h_.promise().caller_ex_ = caller_env->executor;
238 9x tr_.h_.promise().parent_ = cont;
239
240 9x auto h = inner_.handle();
241 9x auto& p = h.promise();
242 9x p.set_continuation(tr_.h_);
243
244 9x env_.executor = ex_;
245 if constexpr (InheritStopToken)
246 5x env_.stop_token = caller_env->stop_token;
247 else
248 4x env_.stop_token = st_;
249
250 if constexpr (!std::is_void_v<Alloc>)
251 4x env_.frame_allocator = resource_.get();
252 else
253 5x env_.frame_allocator = caller_env->frame_allocator;
254
255 9x p.set_environment(&env_);
256 18x return h;
257 }
258
259 // Non-copyable
260 run_awaitable_ex(run_awaitable_ex const&) = delete;
261 run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
262
263 // Movable (no noexcept - Task may throw)
264 9x run_awaitable_ex(run_awaitable_ex&&) = default;
265 run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
266 };
267
268 //----------------------------------------------------------
269 //
270 // run_awaitable - no executor (inherits caller's executor)
271 //
272 //----------------------------------------------------------
273
274 /** Awaitable that runs a task with optional stop_token override.
275
276 Does NOT store an executor - the task inherits the caller's executor
277 directly. Executors always match, so no dispatch trampoline is needed.
278 The inner task's `final_suspend` resumes the parent directly via
279 unconditional symmetric transfer.
280
281 @tparam Task The IoRunnable type
282 @tparam InheritStopToken If true, inherit caller's stop token
283 @tparam Alloc The allocator type (void for no allocator)
284 */
285 template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
286 struct [[nodiscard]] run_awaitable
287 {
288 frame_memory_resource<Alloc> resource_;
289 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
290 io_env env_;
291 Task inner_; // Last: destroyed first, while env_ is still valid
292
293 // void allocator, inherit stop token
294 explicit run_awaitable(Task inner)
295 requires (InheritStopToken && std::is_void_v<Alloc>)
296 : inner_(std::move(inner))
297 {
298 }
299
300 // void allocator, explicit stop token
301 1x run_awaitable(Task inner, std::stop_token st)
302 requires (!InheritStopToken && std::is_void_v<Alloc>)
303 1x : st_(std::move(st))
304 1x , inner_(std::move(inner))
305 {
306 1x }
307
308 // with allocator, inherit stop token (use template to avoid void parameter)
309 template<class A>
310 requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
311 3x run_awaitable(A alloc, Task inner)
312 3x : resource_(std::move(alloc))
313 3x , inner_(std::move(inner))
314 {
315 3x }
316
317 // with allocator, explicit stop token (use template to avoid void parameter)
318 template<class A>
319 requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
320 2x run_awaitable(A alloc, Task inner, std::stop_token st)
321 2x : resource_(std::move(alloc))
322 2x , st_(std::move(st))
323 2x , inner_(std::move(inner))
324 {
325 2x }
326
327 6x bool await_ready() const noexcept
328 {
329 6x return inner_.await_ready();
330 }
331
332 6x decltype(auto) await_resume()
333 {
334 6x return inner_.await_resume();
335 }
336
337 6x std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
338 {
339 6x auto h = inner_.handle();
340 6x auto& p = h.promise();
341 6x p.set_continuation(cont);
342
343 6x env_.executor = caller_env->executor;
344 if constexpr (InheritStopToken)
345 3x env_.stop_token = caller_env->stop_token;
346 else
347 3x env_.stop_token = st_;
348
349 if constexpr (!std::is_void_v<Alloc>)
350 5x env_.frame_allocator = resource_.get();
351 else
352 1x env_.frame_allocator = caller_env->frame_allocator;
353
354 6x p.set_environment(&env_);
355 6x return h;
356 }
357
358 // Non-copyable
359 run_awaitable(run_awaitable const&) = delete;
360 run_awaitable& operator=(run_awaitable const&) = delete;
361
362 // Movable (no noexcept - Task may throw)
363 6x run_awaitable(run_awaitable&&) = default;
364 run_awaitable& operator=(run_awaitable&&) = default;
365 };
366
367 //----------------------------------------------------------
368 //
369 // run_wrapper_ex - with executor
370 //
371 //----------------------------------------------------------
372
373 /** Wrapper returned by run(ex, ...) that accepts a task for execution.
374
375 @tparam Ex The executor type.
376 @tparam InheritStopToken If true, inherit caller's stop token.
377 @tparam Alloc The allocator type (void for no allocator).
378 */
379 template<Executor Ex, bool InheritStopToken, class Alloc>
380 class [[nodiscard]] run_wrapper_ex
381 {
382 Ex ex_;
383 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
384 frame_memory_resource<Alloc> resource_;
385 Alloc alloc_; // Copy to pass to awaitable
386
387 public:
388 1x run_wrapper_ex(Ex ex, Alloc alloc)
389 requires InheritStopToken
390 1x : ex_(std::move(ex))
391 1x , resource_(alloc)
392 1x , alloc_(std::move(alloc))
393 {
394 1x set_current_frame_allocator(&resource_);
395 1x }
396
397 1x run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
398 requires (!InheritStopToken)
399 1x : ex_(std::move(ex))
400 1x , st_(std::move(st))
401 1x , resource_(alloc)
402 1x , alloc_(std::move(alloc))
403 {
404 1x set_current_frame_allocator(&resource_);
405 1x }
406
407 // Non-copyable, non-movable (must be used immediately)
408 run_wrapper_ex(run_wrapper_ex const&) = delete;
409 run_wrapper_ex(run_wrapper_ex&&) = delete;
410 run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
411 run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
412
413 template<IoRunnable Task>
414 2x [[nodiscard]] auto operator()(Task t) &&
415 {
416 if constexpr (InheritStopToken)
417 return run_awaitable_ex<Task, Ex, true, Alloc>{
418 1x std::move(ex_), std::move(alloc_), std::move(t)};
419 else
420 return run_awaitable_ex<Task, Ex, false, Alloc>{
421 1x std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
422 }
423 };
424
425 /// Specialization for memory_resource* - stores pointer directly.
426 template<Executor Ex, bool InheritStopToken>
427 class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
428 {
429 Ex ex_;
430 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
431 std::pmr::memory_resource* mr_;
432
433 public:
434 1x run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
435 requires InheritStopToken
436 1x : ex_(std::move(ex))
437 1x , mr_(mr)
438 {
439 1x set_current_frame_allocator(mr_);
440 1x }
441
442 1x run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
443 requires (!InheritStopToken)
444 1x : ex_(std::move(ex))
445 1x , st_(std::move(st))
446 1x , mr_(mr)
447 {
448 1x set_current_frame_allocator(mr_);
449 1x }
450
451 // Non-copyable, non-movable (must be used immediately)
452 run_wrapper_ex(run_wrapper_ex const&) = delete;
453 run_wrapper_ex(run_wrapper_ex&&) = delete;
454 run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
455 run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
456
457 template<IoRunnable Task>
458 2x [[nodiscard]] auto operator()(Task t) &&
459 {
460 if constexpr (InheritStopToken)
461 return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
462 1x std::move(ex_), mr_, std::move(t)};
463 else
464 return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
465 1x std::move(ex_), mr_, std::move(t), std::move(st_)};
466 }
467 };
468
469 /// Specialization for no allocator (void).
470 template<Executor Ex, bool InheritStopToken>
471 class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
472 {
473 Ex ex_;
474 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
475
476 public:
477 3x explicit run_wrapper_ex(Ex ex)
478 requires InheritStopToken
479 3x : ex_(std::move(ex))
480 {
481 3x }
482
483 2x run_wrapper_ex(Ex ex, std::stop_token st)
484 requires (!InheritStopToken)
485 2x : ex_(std::move(ex))
486 2x , st_(std::move(st))
487 {
488 2x }
489
490 // Non-copyable, non-movable (must be used immediately)
491 run_wrapper_ex(run_wrapper_ex const&) = delete;
492 run_wrapper_ex(run_wrapper_ex&&) = delete;
493 run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
494 run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
495
496 template<IoRunnable Task>
497 5x [[nodiscard]] auto operator()(Task t) &&
498 {
499 if constexpr (InheritStopToken)
500 return run_awaitable_ex<Task, Ex, true>{
501 3x std::move(ex_), std::move(t)};
502 else
503 return run_awaitable_ex<Task, Ex, false>{
504 2x std::move(ex_), std::move(t), std::move(st_)};
505 }
506 };
507
508 //----------------------------------------------------------
509 //
510 // run_wrapper - no executor (inherits caller's executor)
511 //
512 //----------------------------------------------------------
513
514 /** Wrapper returned by run(st) or run(alloc) that accepts a task.
515
516 @tparam InheritStopToken If true, inherit caller's stop token.
517 @tparam Alloc The allocator type (void for no allocator).
518 */
519 template<bool InheritStopToken, class Alloc>
520 class [[nodiscard]] run_wrapper
521 {
522 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
523 frame_memory_resource<Alloc> resource_;
524 Alloc alloc_; // Copy to pass to awaitable
525
526 public:
527 1x explicit run_wrapper(Alloc alloc)
528 requires InheritStopToken
529 1x : resource_(alloc)
530 1x , alloc_(std::move(alloc))
531 {
532 1x set_current_frame_allocator(&resource_);
533 1x }
534
535 1x run_wrapper(std::stop_token st, Alloc alloc)
536 requires (!InheritStopToken)
537 1x : st_(std::move(st))
538 1x , resource_(alloc)
539 1x , alloc_(std::move(alloc))
540 {
541 1x set_current_frame_allocator(&resource_);
542 1x }
543
544 // Non-copyable, non-movable (must be used immediately)
545 run_wrapper(run_wrapper const&) = delete;
546 run_wrapper(run_wrapper&&) = delete;
547 run_wrapper& operator=(run_wrapper const&) = delete;
548 run_wrapper& operator=(run_wrapper&&) = delete;
549
550 template<IoRunnable Task>
551 2x [[nodiscard]] auto operator()(Task t) &&
552 {
553 if constexpr (InheritStopToken)
554 return run_awaitable<Task, true, Alloc>{
555 1x std::move(alloc_), std::move(t)};
556 else
557 return run_awaitable<Task, false, Alloc>{
558 1x std::move(alloc_), std::move(t), std::move(st_)};
559 }
560 };
561
562 /// Specialization for memory_resource* - stores pointer directly.
563 template<bool InheritStopToken>
564 class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
565 {
566 std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
567 std::pmr::memory_resource* mr_;
568
569 public:
570 2x explicit run_wrapper(std::pmr::memory_resource* mr)
571 requires InheritStopToken
572 2x : mr_(mr)
573 {
574 2x set_current_frame_allocator(mr_);
575 2x }
576
577 1x run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
578 requires (!InheritStopToken)
579 1x : st_(std::move(st))
580 1x , mr_(mr)
581 {
582 1x set_current_frame_allocator(mr_);
583 1x }
584
585 // Non-copyable, non-movable (must be used immediately)
586 run_wrapper(run_wrapper const&) = delete;
587 run_wrapper(run_wrapper&&) = delete;
588 run_wrapper& operator=(run_wrapper const&) = delete;
589 run_wrapper& operator=(run_wrapper&&) = delete;
590
591 template<IoRunnable Task>
592 3x [[nodiscard]] auto operator()(Task t) &&
593 {
594 if constexpr (InheritStopToken)
595 return run_awaitable<Task, true, std::pmr::memory_resource*>{
596 2x mr_, std::move(t)};
597 else
598 return run_awaitable<Task, false, std::pmr::memory_resource*>{
599 1x mr_, std::move(t), std::move(st_)};
600 }
601 };
602
603 /// Specialization for stop_token only (no allocator).
604 template<>
605 class [[nodiscard]] run_wrapper<false, void>
606 {
607 std::stop_token st_;
608
609 public:
610 1x explicit run_wrapper(std::stop_token st)
611 1x : st_(std::move(st))
612 {
613 1x }
614
615 // Non-copyable, non-movable (must be used immediately)
616 run_wrapper(run_wrapper const&) = delete;
617 run_wrapper(run_wrapper&&) = delete;
618 run_wrapper& operator=(run_wrapper const&) = delete;
619 run_wrapper& operator=(run_wrapper&&) = delete;
620
621 template<IoRunnable Task>
622 1x [[nodiscard]] auto operator()(Task t) &&
623 {
624 1x return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
625 }
626 };
627
628 } // namespace boost::capy::detail
629
630 namespace boost::capy {
631
632 //----------------------------------------------------------
633 //
634 // run() overloads - with executor
635 //
636 //----------------------------------------------------------
637
638 /** Bind a task to execute on a specific executor.
639
640 Returns a wrapper that accepts a task and produces an awaitable.
641 When co_awaited, the task runs on the specified executor.
642
643 @par Example
644 @code
645 co_await run(other_executor)(my_task());
646 @endcode
647
648 @param ex The executor on which the task should run.
649
650 @return A wrapper that accepts a task for execution.
651
652 @see task
653 @see executor
654 */
655 template<Executor Ex>
656 [[nodiscard]] auto
657 3x run(Ex ex)
658 {
659 3x return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
660 }
661
662 /** Bind a task to an executor with a stop token.
663
664 @param ex The executor on which the task should run.
665 @param st The stop token for cooperative cancellation.
666
667 @return A wrapper that accepts a task for execution.
668 */
669 template<Executor Ex>
670 [[nodiscard]] auto
671 2x run(Ex ex, std::stop_token st)
672 {
673 return detail::run_wrapper_ex<Ex, false, void>{
674 2x std::move(ex), std::move(st)};
675 }
676
677 /** Bind a task to an executor with a memory resource.
678
679 @param ex The executor on which the task should run.
680 @param mr The memory resource for frame allocation.
681
682 @return A wrapper that accepts a task for execution.
683 */
684 template<Executor Ex>
685 [[nodiscard]] auto
686 1x run(Ex ex, std::pmr::memory_resource* mr)
687 {
688 return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
689 1x std::move(ex), mr};
690 }
691
692 /** Bind a task to an executor with a standard allocator.
693
694 @param ex The executor on which the task should run.
695 @param alloc The allocator for frame allocation.
696
697 @return A wrapper that accepts a task for execution.
698 */
699 template<Executor Ex, detail::Allocator Alloc>
700 [[nodiscard]] auto
701 1x run(Ex ex, Alloc alloc)
702 {
703 return detail::run_wrapper_ex<Ex, true, Alloc>{
704 1x std::move(ex), std::move(alloc)};
705 }
706
707 /** Bind a task to an executor with stop token and memory resource.
708
709 @param ex The executor on which the task should run.
710 @param st The stop token for cooperative cancellation.
711 @param mr The memory resource for frame allocation.
712
713 @return A wrapper that accepts a task for execution.
714 */
715 template<Executor Ex>
716 [[nodiscard]] auto
717 1x run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
718 {
719 return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
720 1x std::move(ex), std::move(st), mr};
721 }
722
723 /** Bind a task to an executor with stop token and standard allocator.
724
725 @param ex The executor on which the task should run.
726 @param st The stop token for cooperative cancellation.
727 @param alloc The allocator for frame allocation.
728
729 @return A wrapper that accepts a task for execution.
730 */
731 template<Executor Ex, detail::Allocator Alloc>
732 [[nodiscard]] auto
733 1x run(Ex ex, std::stop_token st, Alloc alloc)
734 {
735 return detail::run_wrapper_ex<Ex, false, Alloc>{
736 1x std::move(ex), std::move(st), std::move(alloc)};
737 }
738
739 //----------------------------------------------------------
740 //
741 // run() overloads - no executor (inherits caller's)
742 //
743 //----------------------------------------------------------
744
745 /** Run a task with a custom stop token.
746
747 The task inherits the caller's executor. Only the stop token
748 is overridden.
749
750 @par Example
751 @code
752 std::stop_source source;
753 co_await run(source.get_token())(cancellable_task());
754 @endcode
755
756 @param st The stop token for cooperative cancellation.
757
758 @return A wrapper that accepts a task for execution.
759 */
760 [[nodiscard]] inline auto
761 1x run(std::stop_token st)
762 {
763 1x return detail::run_wrapper<false, void>{std::move(st)};
764 }
765
766 /** Run a task with a custom memory resource.
767
768 The task inherits the caller's executor. The memory resource
769 is used for nested frame allocations.
770
771 @param mr The memory resource for frame allocation.
772
773 @return A wrapper that accepts a task for execution.
774 */
775 [[nodiscard]] inline auto
776 2x run(std::pmr::memory_resource* mr)
777 {
778 2x return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
779 }
780
781 /** Run a task with a custom standard allocator.
782
783 The task inherits the caller's executor. The allocator is used
784 for nested frame allocations.
785
786 @param alloc The allocator for frame allocation.
787
788 @return A wrapper that accepts a task for execution.
789 */
790 template<detail::Allocator Alloc>
791 [[nodiscard]] auto
792 1x run(Alloc alloc)
793 {
794 1x return detail::run_wrapper<true, Alloc>{std::move(alloc)};
795 }
796
797 /** Run a task with stop token and memory resource.
798
799 The task inherits the caller's executor.
800
801 @param st The stop token for cooperative cancellation.
802 @param mr The memory resource for frame allocation.
803
804 @return A wrapper that accepts a task for execution.
805 */
806 [[nodiscard]] inline auto
807 1x run(std::stop_token st, std::pmr::memory_resource* mr)
808 {
809 return detail::run_wrapper<false, std::pmr::memory_resource*>{
810 1x std::move(st), mr};
811 }
812
813 /** Run a task with stop token and standard allocator.
814
815 The task inherits the caller's executor.
816
817 @param st The stop token for cooperative cancellation.
818 @param alloc The allocator for frame allocation.
819
820 @return A wrapper that accepts a task for execution.
821 */
822 template<detail::Allocator Alloc>
823 [[nodiscard]] auto
824 1x run(std::stop_token st, Alloc alloc)
825 {
826 return detail::run_wrapper<false, Alloc>{
827 1x std::move(st), std::move(alloc)};
828 }
829
830 } // namespace boost::capy
831
832 #endif
833