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