include/boost/capy/ex/run.hpp

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