include/boost/capy/ex/run.hpp

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