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