100.00% Lines (271/271) 100.00% Functions (64/64)
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/context_runnable.hpp>
18   #include <boost/capy/ex/abandon.hpp> 18   #include <boost/capy/ex/abandon.hpp>
19   #include <boost/capy/ex/executor_ref.hpp> 19   #include <boost/capy/ex/executor_ref.hpp>
20   #include <coroutine> 20   #include <coroutine>
21   #include <boost/capy/ex/frame_alloc_mixin.hpp> 21   #include <boost/capy/ex/frame_alloc_mixin.hpp>
22   #include <boost/capy/ex/frame_allocator.hpp> 22   #include <boost/capy/ex/frame_allocator.hpp>
23 - #include <boost/capy/ex/io_env.hpp> 23 + #include <boost/capy/ex/await_context.hpp>
24   24  
25   #include <exception> 25   #include <exception>
26   #include <memory_resource> 26   #include <memory_resource>
27   #include <stop_token> 27   #include <stop_token>
28   #include <type_traits> 28   #include <type_traits>
29   #include <utility> 29   #include <utility>
30   #include <variant> 30   #include <variant>
31   31  
32   /* 32   /*
33   Allocator Lifetime Strategy 33   Allocator Lifetime Strategy
34   =========================== 34   ===========================
35   35  
36   When using run() with a custom allocator: 36   When using run() with a custom allocator:
37   37  
38   co_await run(ex, alloc)(my_task()); 38   co_await run(ex, alloc)(my_task());
39   39  
40   The wrapper run(ex, alloc) returns is a temporary holding the 40   The wrapper run(ex, alloc) returns is a temporary holding the
41   frame_memory_resource my_task()'s frame is allocated from, so it must 41   frame_memory_resource my_task()'s frame is allocated from, so it must
42   live until that frame is freed: the awaitable is awaited in the 42   live until that frame is freed: the awaitable is awaited in the
43   full-expression that made the wrapper, and the temporary lives until 43   full-expression that made the wrapper, and the temporary lives until
44   the co_await completes. Keeping the awaitable and awaiting it later 44   the co_await completes. Keeping the awaitable and awaiting it later
45   frees the task's frame through a dead resource. 45   frees the task's frame through a dead resource.
46   46  
47   The awaitable also holds a copy of the allocator. In await_suspend it 47   The awaitable also holds a copy of the allocator. In await_suspend it
48   overwrites the promise's saved frame_allocator pointer with the 48   overwrites the promise's saved frame_allocator pointer with the
49   awaitable's resource, so tasks the inner task creates allocate from 49   awaitable's resource, so tasks the inner task creates allocate from
50   it through TLS. 50   it through TLS.
51   */ 51   */
52   52  
53   namespace boost::capy::detail { 53   namespace boost::capy::detail {
54   54  
55   /** Minimal coroutine that dispatches through the caller's executor. 55   /** Minimal coroutine that dispatches through the caller's executor.
56   56  
57   Sits between the inner task and the parent when executors 57   Sits between the inner task and the parent when executors
58   diverge. The inner task's `final_suspend` resumes this 58   diverge. The inner task's `final_suspend` resumes this
59   trampoline via symmetric transfer. The trampoline's own 59   trampoline via symmetric transfer. The trampoline's own
60   `final_suspend` posts the parent through the caller's executor to 60   `final_suspend` posts the parent through the caller's executor to
61   restore the correct execution context. 61   restore the correct execution context.
62   62  
63   It also owns the inner task's segment. Abandoning that segment 63   It also owns the inner task's segment. Abandoning that segment
64   resumes the trampoline on the inner executor, or has a closed 64   resumes the trampoline on the inner executor, or has a closed
65   executor destroy it: either way it destroys the inner task, which 65   executor destroy it: either way it destroys the inner task, which
66   unwinds the segment, then abandons the caller's segment, which 66   unwinds the segment, then abandons the caller's segment, which
67   unwinds the parent on its own executor. The caller's execution 67   unwinds the parent on its own executor. The caller's execution
68   context must outlive that. 68   context must outlive that.
69   */ 69   */
70   struct boundary_trampoline 70   struct boundary_trampoline
71   { 71   {
72   struct promise_type 72   struct promise_type
73   : detail::process_frame_mixin 73   : detail::process_frame_mixin
74   { 74   {
75   executor_ref caller_ex_; 75   executor_ref caller_ex_;
76   continuation parent_; 76   continuation parent_;
77   chain_owner segment_; 77   chain_owner segment_;
78   // The awaitable's trampoline object until the inner task 78   // The awaitable's trampoline object until the inner task
79   // completes or is unwound; null afterward. 79   // completes or is unwound; null afterward.
80   boundary_trampoline* owner_ = nullptr; 80   boundary_trampoline* owner_ = nullptr;
81   // Destroys the inner task the awaitable owns. 81   // Destroys the inner task the awaitable owns.
82   void (*destroy_inner_)(void*) noexcept = nullptr; 82   void (*destroy_inner_)(void*) noexcept = nullptr;
83   void* awaitable_ = nullptr; 83   void* awaitable_ = nullptr;
84   84  
85   // Unwind the inner segment, then hand the parent to its own 85   // Unwind the inner segment, then hand the parent to its own
86   // segment's owner. The last touch of this frame: abandoning the 86   // segment's owner. The last touch of this frame: abandoning the
87   // parent can destroy the awaitable, and this frame with it. 87   // parent can destroy the awaitable, and this frame with it.
HITCBC 88   10 void unwind() noexcept 88   10 void unwind() noexcept
89   { 89   {
HITCBC 90   10 destroy_inner_(awaitable_); 90   10 destroy_inner_(awaitable_);
HITCBC 91   10 owner_ = nullptr; 91   10 owner_ = nullptr;
HITCBC 92   10 auto parent = parent_; 92   10 auto parent = parent_;
HITCBC 93   10 capy::abandon(parent); 93   10 capy::abandon(parent);
HITCBC 94   10 } 94   10 }
95   95  
HITCBC 96   87 ~promise_type() 96   87 ~promise_type()
97   { 97   {
98   // Linked only when a closed executor destroyed this frame 98   // Linked only when a closed executor destroyed this frame
99   // instead of resuming it to unwind. 99   // instead of resuming it to unwind.
HITCBC 100   87 if(owner_) 100   87 if(owner_)
101   { 101   {
HITCBC 102   1 owner_->h_ = nullptr; 102   1 owner_->h_ = nullptr;
HITCBC 103   1 unwind(); 103   1 unwind();
104   } 104   }
HITCBC 105   87 } 105   87 }
106   106  
HITCBC 107   87 boundary_trampoline get_return_object() noexcept 107   87 boundary_trampoline get_return_object() noexcept
108   { 108   {
109   return boundary_trampoline{ 109   return boundary_trampoline{
HITCBC 110   87 std::coroutine_handle<promise_type>::from_promise(*this)}; 110   87 std::coroutine_handle<promise_type>::from_promise(*this)};
111   } 111   }
112   112  
HITCBC 113   87 std::suspend_always initial_suspend() noexcept { return {}; } 113   87 std::suspend_always initial_suspend() noexcept { return {}; }
114   114  
HITCBC 115   85 auto final_suspend() noexcept 115   85 auto final_suspend() noexcept
116   { 116   {
117   struct awaiter 117   struct awaiter
118   { 118   {
119   promise_type* p_; 119   promise_type* p_;
HITCBC 120   85 bool await_ready() const noexcept { return false; } 120   85 bool await_ready() const noexcept { return false; }
121   121  
HITCBC 122   85 auto await_suspend( 122   85 auto await_suspend(
123   std::coroutine_handle<>) noexcept 123   std::coroutine_handle<>) noexcept
124   { 124   {
125   // Nothing here touches p_ after the post or the 125   // Nothing here touches p_ after the post or the
126   // unwind: either can destroy this frame. 126   // unwind: either can destroy this frame.
HITCBC 127   85 if(p_->segment_.abandoned()) 127   85 if(p_->segment_.abandoned())
128   { 128   {
HITCBC 129   9 p_->unwind(); 129   9 p_->unwind();
130   } 130   }
131   else 131   else
132   { 132   {
HITCBC 133   76 p_->owner_ = nullptr; 133   76 p_->owner_ = nullptr;
HITCBC 134   76 auto ex = p_->caller_ex_; 134   76 auto ex = p_->caller_ex_;
HITCBC 135   76 ex.post(p_->parent_); 135   76 ex.post(p_->parent_);
136   } 136   }
HITCBC 137   85 return detail::symmetric_transfer( 137   85 return detail::symmetric_transfer(
HITCBC 138   170 std::noop_coroutine()); 138   170 std::noop_coroutine());
139   } 139   }
140   140  
141   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 141   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
142   }; 142   };
HITCBC 143   85 return awaiter{this}; 143   85 return awaiter{this};
144   } 144   }
145   145  
HITCBC 146   85 void return_void() noexcept {} 146   85 void return_void() noexcept {}
147   void unhandled_exception() noexcept {} 147   void unhandled_exception() noexcept {}
148   }; 148   };
149   149  
150   std::coroutine_handle<promise_type> h_{nullptr}; 150   std::coroutine_handle<promise_type> h_{nullptr};
151   151  
HITCBC 152   88 boundary_trampoline() noexcept = default; 152   88 boundary_trampoline() noexcept = default;
153   153  
HITCBC 154   261 ~boundary_trampoline() 154   261 ~boundary_trampoline()
155   { 155   {
HITCBC 156   261 if(h_) 156   261 if(h_)
157   { 157   {
HITCBC 158   85 h_.promise().owner_ = nullptr; 158   85 h_.promise().owner_ = nullptr;
HITCBC 159   85 h_.destroy(); 159   85 h_.destroy();
160   } 160   }
HITCBC 161   261 } 161   261 }
162   162  
163   boundary_trampoline(boundary_trampoline const&) = delete; 163   boundary_trampoline(boundary_trampoline const&) = delete;
164   boundary_trampoline& operator=(boundary_trampoline const&) = delete; 164   boundary_trampoline& operator=(boundary_trampoline const&) = delete;
165   165  
HITCBC 166   86 boundary_trampoline(boundary_trampoline&& o) noexcept 166   86 boundary_trampoline(boundary_trampoline&& o) noexcept
HITCBC 167   86 : h_(std::exchange(o.h_, nullptr)) {} 167   86 : h_(std::exchange(o.h_, nullptr)) {}
168   168  
HITCBC 169   88 boundary_trampoline& operator=(boundary_trampoline&& o) noexcept 169   88 boundary_trampoline& operator=(boundary_trampoline&& o) noexcept
170   { 170   {
HITCBC 171   88 if(this != &o) 171   88 if(this != &o)
172   { 172   {
HITCBC 173   88 if(h_) 173   88 if(h_)
174   { 174   {
HITCBC 175   1 h_.promise().owner_ = nullptr; 175   1 h_.promise().owner_ = nullptr;
HITCBC 176   1 h_.destroy(); 176   1 h_.destroy();
177   } 177   }
HITCBC 178   88 h_ = std::exchange(o.h_, nullptr); 178   88 h_ = std::exchange(o.h_, nullptr);
179   } 179   }
HITCBC 180   88 return *this; 180   88 return *this;
181   } 181   }
182   182  
183   private: 183   private:
HITCBC 184   87 explicit boundary_trampoline(std::coroutine_handle<promise_type> h) noexcept 184   87 explicit boundary_trampoline(std::coroutine_handle<promise_type> h) noexcept
HITCBC 185   87 : h_(h) {} 185   87 : h_(h) {}
186   }; 186   };
187   187  
HITCBC 188   87 inline boundary_trampoline make_boundary_trampoline() 188   87 inline boundary_trampoline make_boundary_trampoline()
189   { 189   {
190   co_return; 190   co_return;
HITCBC 191   174 } 191   174 }
192   192  
193 - /** Awaitable that binds an IoRunnable to a specific executor. 193 + /** Awaitable that binds a ContextRunnable to a specific executor.
194   194  
195   Stores the executor and inner task by value. When co_awaited, the 195   Stores the executor and inner task by value. When co_awaited, the
196   co_await expression's lifetime extension keeps both alive for the 196   co_await expression's lifetime extension keeps both alive for the
197   duration of the operation. 197   duration of the operation.
198   198  
199   A dispatch trampoline handles the executor switch on completion: 199   A dispatch trampoline handles the executor switch on completion:
200   the inner task's `final_suspend` resumes the trampoline, which 200   the inner task's `final_suspend` resumes the trampoline, which
201   dispatches back through the caller's executor. 201   dispatches back through the caller's executor.
202   202  
203 - The `io_env` is owned by this awaitable and is guaranteed to 203 + The `await_context` is owned by this awaitable and is guaranteed to
204   outlive the inner task and all awaitables in its chain. Awaitables 204   outlive the inner task and all awaitables in its chain. Awaitables
205 - may store `io_env const*` without concern for dangling references. 205 + may store `await_context const*` without concern for dangling references.
206   206  
207 - @tparam Task The IoRunnable type 207 + @tparam Task The ContextRunnable type
208   @tparam Ex The executor type 208   @tparam Ex The executor type
209   @tparam InheritStopToken If true, inherit caller's stop token 209   @tparam InheritStopToken If true, inherit caller's stop token
210   @tparam Alloc The allocator type (void for no allocator) 210   @tparam Alloc The allocator type (void for no allocator)
211   */ 211   */
212 - template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void> 212 + template<ContextRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
213   struct [[nodiscard]] run_awaitable_ex 213   struct [[nodiscard]] run_awaitable_ex
214   { 214   {
215   Ex ex_; 215   Ex ex_;
216   frame_memory_resource<Alloc> resource_; 216   frame_memory_resource<Alloc> resource_;
217   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_; 217   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
218 - io_env env_; 218 + await_context env_;
219   executor_ref caller_ex_; 219   executor_ref caller_ex_;
220   boundary_trampoline tr_; 220   boundary_trampoline tr_;
221   continuation task_cont_; 221   continuation task_cont_;
222   bool holds_work_ = false; 222   bool holds_work_ = false;
223   Task inner_; // Last: destroyed first, while env_ is still valid 223   Task inner_; // Last: destroyed first, while env_ is still valid
224   224  
HITCBC 225   173 ~run_awaitable_ex() 225   173 ~run_awaitable_ex()
226   { 226   {
227   // Only a parent destroyed from above leaves the trampoline 227   // Only a parent destroyed from above leaves the trampoline
228   // still linked: the inner task is still running. 228   // still linked: the inner task is still running.
HITCBC 229   173 if(tr_.h_ && tr_.h_.promise().owner_) 229   173 if(tr_.h_ && tr_.h_.promise().owner_)
230   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process 230   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
231   // A parent unwound instead of resumed never reaches 231   // A parent unwound instead of resumed never reaches
232   // await_resume. 232   // await_resume.
HITCBC 233   173 if(holds_work_) 233   173 if(holds_work_)
HITCBC 234   11 caller_ex_.on_work_finished(); 234   11 caller_ex_.on_work_finished();
HITCBC 235   173 } 235   173 }
236   236  
237   // Called by the trampoline to unwind the inner segment. 237   // Called by the trampoline to unwind the inner segment.
HITCBC 238   10 static void destroy_inner(void* p) noexcept 238   10 static void destroy_inner(void* p) noexcept
239   { 239   {
HITCBC 240   10 auto& self = *static_cast<run_awaitable_ex*>(p); 240   10 auto& self = *static_cast<run_awaitable_ex*>(p);
HITCBC 241   10 auto h = self.inner_.handle(); 241   10 auto h = self.inner_.handle();
HITCBC 242   10 self.inner_.release(); 242   10 self.inner_.release();
HITCBC 243   10 h.destroy(); 243   10 h.destroy();
HITCBC 244   10 } 244   10 }
245   245  
246   // void allocator, inherit stop token 246   // void allocator, inherit stop token
HITCBC 247   75 run_awaitable_ex(Ex ex, Task inner) 247   75 run_awaitable_ex(Ex ex, Task inner)
248   requires (InheritStopToken && std::is_void_v<Alloc>) 248   requires (InheritStopToken && std::is_void_v<Alloc>)
HITCBC 249   75 : ex_(std::move(ex)) 249   75 : ex_(std::move(ex))
HITCBC 250   75 , inner_(std::move(inner)) 250   75 , inner_(std::move(inner))
251   { 251   {
HITCBC 252   75 } 252   75 }
253   253  
254   // void allocator, explicit stop token 254   // void allocator, explicit stop token
HITCBC 255   5 run_awaitable_ex(Ex ex, Task inner, std::stop_token st) 255   5 run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
256   requires (!InheritStopToken && std::is_void_v<Alloc>) 256   requires (!InheritStopToken && std::is_void_v<Alloc>)
HITCBC 257   5 : ex_(std::move(ex)) 257   5 : ex_(std::move(ex))
HITCBC 258   5 , st_(std::move(st)) 258   5 , st_(std::move(st))
HITCBC 259   5 , inner_(std::move(inner)) 259   5 , inner_(std::move(inner))
260   { 260   {
HITCBC 261   5 } 261   5 }
262   262  
263   // with allocator, inherit stop token (use template to avoid void parameter) 263   // with allocator, inherit stop token (use template to avoid void parameter)
264   template<class A> 264   template<class A>
265   requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>) 265   requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
HITCBC 266   5 run_awaitable_ex(Ex ex, A alloc, Task inner) 266   5 run_awaitable_ex(Ex ex, A alloc, Task inner)
HITCBC 267   5 : ex_(std::move(ex)) 267   5 : ex_(std::move(ex))
HITCBC 268   5 , resource_(std::move(alloc)) 268   5 , resource_(std::move(alloc))
HITCBC 269   5 , inner_(std::move(inner)) 269   5 , inner_(std::move(inner))
270   { 270   {
HITCBC 271   5 } 271   5 }
272   272  
273   // with allocator, explicit stop token (use template to avoid void parameter) 273   // with allocator, explicit stop token (use template to avoid void parameter)
274   template<class A> 274   template<class A>
275   requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>) 275   requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
HITCBC 276   2 run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st) 276   2 run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
HITCBC 277   2 : ex_(std::move(ex)) 277   2 : ex_(std::move(ex))
HITCBC 278   2 , resource_(std::move(alloc)) 278   2 , resource_(std::move(alloc))
HITCBC 279   2 , st_(std::move(st)) 279   2 , st_(std::move(st))
HITCBC 280   2 , inner_(std::move(inner)) 280   2 , inner_(std::move(inner))
281   { 281   {
HITCBC 282   2 } 282   2 }
283   283  
HITCBC 284   86 bool await_ready() const noexcept 284   86 bool await_ready() const noexcept
285   { 285   {
HITCBC 286   86 return inner_.await_ready(); 286   86 return inner_.await_ready();
287   } 287   }
288   288  
HITCBC 289   75 decltype(auto) await_resume() 289   75 decltype(auto) await_resume()
290   { 290   {
HITCBC 291   75 if(std::exchange(holds_work_, false)) 291   75 if(std::exchange(holds_work_, false))
HITCBC 292   75 caller_ex_.on_work_finished(); 292   75 caller_ex_.on_work_finished();
HITCBC 293   75 return inner_.await_resume(); 293   75 return inner_.await_resume();
294   } 294   }
295   295  
HITCBC 296 - 87 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env) 296 + 87 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, await_context const* caller_env)
297   { 297   {
HITCBC 298   87 tr_ = make_boundary_trampoline(); 298   87 tr_ = make_boundary_trampoline();
HITCBC 299   87 auto& tp = tr_.h_.promise(); 299   87 auto& tp = tr_.h_.promise();
HITCBC 300   87 tp.owner_ = &tr_; 300   87 tp.owner_ = &tr_;
HITCBC 301   87 tp.caller_ex_ = caller_env->executor; 301   87 tp.caller_ex_ = caller_env->executor;
HITCBC 302   87 tp.parent_.h = cont; 302   87 tp.parent_.h = cont;
HITCBC 303   87 tp.parent_.owner = caller_env->owner; 303   87 tp.parent_.owner = caller_env->owner;
HITCBC 304   87 tp.destroy_inner_ = &run_awaitable_ex::destroy_inner; 304   87 tp.destroy_inner_ = &run_awaitable_ex::destroy_inner;
HITCBC 305   87 tp.awaitable_ = this; 305   87 tp.awaitable_ = this;
HITCBC 306   87 tp.segment_.bind(tr_.h_, ex_); 306   87 tp.segment_.bind(tr_.h_, ex_);
HITCBC 307   87 caller_ex_ = caller_env->executor; 307   87 caller_ex_ = caller_env->executor;
308   // Released once the parent resumes or is unwound. Not on the 308   // Released once the parent resumes or is unwound. Not on the
309   // post back: the parent's chain may own the executor object and 309   // post back: the parent's chain may own the executor object and
310   // can be gone by the time post returns. 310   // can be gone by the time post returns.
HITCBC 311   87 caller_ex_.on_work_started(); 311   87 caller_ex_.on_work_started();
HITCBC 312   87 holds_work_ = true; 312   87 holds_work_ = true;
313   313  
HITCBC 314   87 auto h = inner_.handle(); 314   87 auto h = inner_.handle();
HITCBC 315   87 auto& p = h.promise(); 315   87 auto& p = h.promise();
HITCBC 316   87 p.set_continuation(tr_.h_); 316   87 p.set_continuation(tr_.h_);
317   317  
HITCBC 318   87 env_.owner = &tp.segment_; 318   87 env_.owner = &tp.segment_;
HITCBC 319   87 env_.executor = ex_; 319   87 env_.executor = ex_;
320   if constexpr (InheritStopToken) 320   if constexpr (InheritStopToken)
HITCBC 321   80 env_.stop_token = caller_env->stop_token; 321   80 env_.stop_token = caller_env->stop_token;
322   else 322   else
HITCBC 323   7 env_.stop_token = st_; 323   7 env_.stop_token = st_;
324   324  
325   if constexpr (!std::is_void_v<Alloc>) 325   if constexpr (!std::is_void_v<Alloc>)
HITCBC 326   7 env_.frame_allocator = resource_.get(); 326   7 env_.frame_allocator = resource_.get();
327   else 327   else
HITCBC 328   80 env_.frame_allocator = caller_env->frame_allocator; 328   80 env_.frame_allocator = caller_env->frame_allocator;
329   329  
HITCBC 330   87 p.set_environment(&env_); 330   87 p.set_environment(&env_);
HITCBC 331   87 task_cont_.h = h; 331   87 task_cont_.h = h;
HITCBC 332   87 task_cont_.owner = &tp.segment_; 332   87 task_cont_.owner = &tp.segment_;
333   try 333   try
334   { 334   {
HITCBC 335   87 ex_.post(task_cont_); 335   87 ex_.post(task_cont_);
336   } 336   }
HITCBC 337   2 catch(...) 337   2 catch(...)
338   { 338   {
339   // The parent resumes with the exception: nothing may still 339   // The parent resumes with the exception: nothing may still
340   // treat the task as lent or hold work for the parent. 340   // treat the task as lent or hold work for the parent.
HITCBC 341   1 p.set_continuation(std::noop_coroutine()); 341   1 p.set_continuation(std::noop_coroutine());
HITCBC 342   1 task_cont_.h = nullptr; 342   1 task_cont_.h = nullptr;
HITCBC 343   1 tr_ = boundary_trampoline(); 343   1 tr_ = boundary_trampoline();
HITCBC 344   1 holds_work_ = false; 344   1 holds_work_ = false;
HITCBC 345   1 caller_ex_.on_work_finished(); 345   1 caller_ex_.on_work_finished();
HITCBC 346   1 throw; 346   1 throw;
347   } 347   }
HITCBC 348   86 return std::noop_coroutine(); 348   86 return std::noop_coroutine();
349   } 349   }
350   350  
351   // Non-copyable 351   // Non-copyable
352   run_awaitable_ex(run_awaitable_ex const&) = delete; 352   run_awaitable_ex(run_awaitable_ex const&) = delete;
353   run_awaitable_ex& operator=(run_awaitable_ex const&) = delete; 353   run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
354   354  
355   // Movable (no noexcept - Task may throw) 355   // Movable (no noexcept - Task may throw)
HITCBC 356   86 run_awaitable_ex(run_awaitable_ex&&) = default; 356   86 run_awaitable_ex(run_awaitable_ex&&) = default;
357   run_awaitable_ex& operator=(run_awaitable_ex&&) = default; 357   run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
358   }; 358   };
359   359  
360   /** Awaitable that runs a task with optional stop_token override. 360   /** Awaitable that runs a task with optional stop_token override.
361   361  
362   Does NOT store an executor - the task inherits the caller's executor 362   Does NOT store an executor - the task inherits the caller's executor
363   directly. Executors always match, so no dispatch trampoline is needed. 363   directly. Executors always match, so no dispatch trampoline is needed.
364   The inner task's `final_suspend` resumes the parent directly via 364   The inner task's `final_suspend` resumes the parent directly via
365   unconditional symmetric transfer. 365   unconditional symmetric transfer.
366   366  
367 - @tparam Task The IoRunnable type 367 + @tparam Task The ContextRunnable type
368   @tparam InheritStopToken If true, inherit caller's stop token 368   @tparam InheritStopToken If true, inherit caller's stop token
369   @tparam Alloc The allocator type (void for no allocator) 369   @tparam Alloc The allocator type (void for no allocator)
370   */ 370   */
371 - template<IoRunnable Task, bool InheritStopToken, class Alloc = void> 371 + template<ContextRunnable Task, bool InheritStopToken, class Alloc = void>
372   struct [[nodiscard]] run_awaitable 372   struct [[nodiscard]] run_awaitable
373   { 373   {
374   frame_memory_resource<Alloc> resource_; 374   frame_memory_resource<Alloc> resource_;
375   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_; 375   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
376 - io_env env_; 376 + await_context env_;
377   // Set while the parent is suspended awaiting the inner task. 377   // Set while the parent is suspended awaiting the inner task.
378   bool lent_ = false; 378   bool lent_ = false;
379   Task inner_; // Last: destroyed first, while env_ is still valid 379   Task inner_; // Last: destroyed first, while env_ is still valid
380   380  
HITCBC 381   36 ~run_awaitable() 381   36 ~run_awaitable()
382   { 382   {
383   // Only the unwind of an abandoned chain destroys an inner task 383   // Only the unwind of an abandoned chain destroys an inner task
384   // still awaited; checked here so it terminates whatever the 384   // still awaited; checked here so it terminates whatever the
385   // task type does when destroyed while lent. 385   // task type does when destroyed while lent.
HITCBC 386   36 auto h = inner_.handle(); 386   36 auto h = inner_.handle();
HITCBC 387   46 if(lent_ && h && !h.done() && 387   46 if(lent_ && h && !h.done() &&
HITCBC 388   10 !(env_.owner && env_.owner->abandoned())) 388   10 !(env_.owner && env_.owner->abandoned()))
389   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process 389   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
HITCBC 390   36 } 390   36 }
391   391  
392   // void allocator, inherit stop token 392   // void allocator, inherit stop token
393   explicit run_awaitable(Task inner) 393   explicit run_awaitable(Task inner)
394   requires (InheritStopToken && std::is_void_v<Alloc>) 394   requires (InheritStopToken && std::is_void_v<Alloc>)
395   : inner_(std::move(inner)) 395   : inner_(std::move(inner))
396   { 396   {
397   } 397   }
398   398  
399   // void allocator, explicit stop token 399   // void allocator, explicit stop token
HITCBC 400   2 run_awaitable(Task inner, std::stop_token st) 400   2 run_awaitable(Task inner, std::stop_token st)
401   requires (!InheritStopToken && std::is_void_v<Alloc>) 401   requires (!InheritStopToken && std::is_void_v<Alloc>)
HITCBC 402   2 : st_(std::move(st)) 402   2 : st_(std::move(st))
HITCBC 403   2 , inner_(std::move(inner)) 403   2 , inner_(std::move(inner))
404   { 404   {
HITCBC 405   2 } 405   2 }
406   406  
407   // with allocator, inherit stop token (use template to avoid void parameter) 407   // with allocator, inherit stop token (use template to avoid void parameter)
408   template<class A> 408   template<class A>
409   requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>) 409   requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
HITCBC 410   14 run_awaitable(A alloc, Task inner) 410   14 run_awaitable(A alloc, Task inner)
HITCBC 411   14 : resource_(std::move(alloc)) 411   14 : resource_(std::move(alloc))
HITCBC 412   14 , inner_(std::move(inner)) 412   14 , inner_(std::move(inner))
413   { 413   {
HITCBC 414   14 } 414   14 }
415   415  
416   // with allocator, explicit stop token (use template to avoid void parameter) 416   // with allocator, explicit stop token (use template to avoid void parameter)
417   template<class A> 417   template<class A>
418   requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>) 418   requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
HITCBC 419   2 run_awaitable(A alloc, Task inner, std::stop_token st) 419   2 run_awaitable(A alloc, Task inner, std::stop_token st)
HITCBC 420   2 : resource_(std::move(alloc)) 420   2 : resource_(std::move(alloc))
HITCBC 421   2 , st_(std::move(st)) 421   2 , st_(std::move(st))
HITCBC 422   2 , inner_(std::move(inner)) 422   2 , inner_(std::move(inner))
423   { 423   {
HITCBC 424   2 } 424   2 }
425   425  
HITCBC 426   18 bool await_ready() const noexcept 426   18 bool await_ready() const noexcept
427   { 427   {
HITCBC 428   18 return inner_.await_ready(); 428   18 return inner_.await_ready();
429   } 429   }
430   430  
HITCBC 431   8 decltype(auto) await_resume() 431   8 decltype(auto) await_resume()
432   { 432   {
HITCBC 433   8 lent_ = false; 433   8 lent_ = false;
HITCBC 434   8 return inner_.await_resume(); 434   8 return inner_.await_resume();
435   } 435   }
436   436  
HITCBC 437 - 18 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env) 437 + 18 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, await_context const* caller_env)
438   { 438   {
HITCBC 439   18 auto h = inner_.handle(); 439   18 auto h = inner_.handle();
HITCBC 440   18 auto& p = h.promise(); 440   18 auto& p = h.promise();
HITCBC 441   18 p.set_continuation(cont); 441   18 p.set_continuation(cont);
HITCBC 442   18 lent_ = true; 442   18 lent_ = true;
443   443  
HITCBC 444   18 env_.owner = caller_env->owner; 444   18 env_.owner = caller_env->owner;
HITCBC 445   18 env_.executor = caller_env->executor; 445   18 env_.executor = caller_env->executor;
446   if constexpr (InheritStopToken) 446   if constexpr (InheritStopToken)
HITCBC 447   14 env_.stop_token = caller_env->stop_token; 447   14 env_.stop_token = caller_env->stop_token;
448   else 448   else
HITCBC 449   4 env_.stop_token = st_; 449   4 env_.stop_token = st_;
450   450  
451   if constexpr (!std::is_void_v<Alloc>) 451   if constexpr (!std::is_void_v<Alloc>)
HITCBC 452   16 env_.frame_allocator = resource_.get(); 452   16 env_.frame_allocator = resource_.get();
453   else 453   else
HITCBC 454   2 env_.frame_allocator = caller_env->frame_allocator; 454   2 env_.frame_allocator = caller_env->frame_allocator;
455   455  
HITCBC 456   18 p.set_environment(&env_); 456   18 p.set_environment(&env_);
HITCBC 457   18 return h; 457   18 return h;
458   } 458   }
459   459  
460   // Non-copyable 460   // Non-copyable
461   run_awaitable(run_awaitable const&) = delete; 461   run_awaitable(run_awaitable const&) = delete;
462   run_awaitable& operator=(run_awaitable const&) = delete; 462   run_awaitable& operator=(run_awaitable const&) = delete;
463   463  
464   // Movable (no noexcept - Task may throw) 464   // Movable (no noexcept - Task may throw)
HITCBC 465   18 run_awaitable(run_awaitable&&) = default; 465   18 run_awaitable(run_awaitable&&) = default;
466   run_awaitable& operator=(run_awaitable&&) = default; 466   run_awaitable& operator=(run_awaitable&&) = default;
467   }; 467   };
468   468  
469   /** Wrapper returned by run(ex, ...) that accepts a task for execution. 469   /** Wrapper returned by run(ex, ...) that accepts a task for execution.
470   470  
471   @tparam Ex The executor type. 471   @tparam Ex The executor type.
472   @tparam InheritStopToken If true, inherit caller's stop token. 472   @tparam InheritStopToken If true, inherit caller's stop token.
473   @tparam Alloc The allocator type (void for no allocator). 473   @tparam Alloc The allocator type (void for no allocator).
474   */ 474   */
475   template<Executor Ex, bool InheritStopToken, class Alloc> 475   template<Executor Ex, bool InheritStopToken, class Alloc>
476   class [[nodiscard]] run_wrapper_ex 476   class [[nodiscard]] run_wrapper_ex
477   { 477   {
478   Ex ex_; 478   Ex ex_;
479   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_; 479   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
480   frame_memory_resource<Alloc> resource_; 480   frame_memory_resource<Alloc> resource_;
481   Alloc alloc_; // Copy to pass to awaitable 481   Alloc alloc_; // Copy to pass to awaitable
482   482  
483   std::pmr::memory_resource* saved_tls_ = 483   std::pmr::memory_resource* saved_tls_ =
484   get_current_frame_allocator(); 484   get_current_frame_allocator();
485   bool restored_ = false; 485   bool restored_ = false;
486   486  
487   public: 487   public:
HITCBC 488   3 run_wrapper_ex(Ex ex, Alloc alloc) 488   3 run_wrapper_ex(Ex ex, Alloc alloc)
489   requires InheritStopToken 489   requires InheritStopToken
HITCBC 490   3 : ex_(std::move(ex)) 490   3 : ex_(std::move(ex))
HITCBC 491   3 , resource_(alloc) 491   3 , resource_(alloc)
HITCBC 492   3 , alloc_(std::move(alloc)) 492   3 , alloc_(std::move(alloc))
493   { 493   {
HITCBC 494   3 set_current_frame_allocator(&resource_); 494   3 set_current_frame_allocator(&resource_);
HITCBC 495   3 } 495   3 }
496   496  
HITCBC 497   1 run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc) 497   1 run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
498   requires (!InheritStopToken) 498   requires (!InheritStopToken)
HITCBC 499   1 : ex_(std::move(ex)) 499   1 : ex_(std::move(ex))
HITCBC 500   1 , st_(std::move(st)) 500   1 , st_(std::move(st))
HITCBC 501   1 , resource_(alloc) 501   1 , resource_(alloc)
HITCBC 502   1 , alloc_(std::move(alloc)) 502   1 , alloc_(std::move(alloc))
503   { 503   {
HITCBC 504   1 set_current_frame_allocator(&resource_); 504   1 set_current_frame_allocator(&resource_);
HITCBC 505   1 } 505   1 }
506   506  
507   // Restores the caller's allocator when evaluating the task argument 507   // Restores the caller's allocator when evaluating the task argument
508   // threw. Not afterwards: a wrapper destroyed with a torn-down parent 508   // threw. Not afterwards: a wrapper destroyed with a torn-down parent
509   // frame must not touch the tearing thread's allocator. 509   // frame must not touch the tearing thread's allocator.
HITCBC 510   4 ~run_wrapper_ex() 510   4 ~run_wrapper_ex()
511   { 511   {
HITCBC 512   4 if(!restored_) 512   4 if(!restored_)
HITCBC 513   1 set_current_frame_allocator(saved_tls_); 513   1 set_current_frame_allocator(saved_tls_);
HITCBC 514   4 } 514   4 }
515   515  
516   // Non-copyable, non-movable (must be used immediately) 516   // Non-copyable, non-movable (must be used immediately)
517   run_wrapper_ex(run_wrapper_ex const&) = delete; 517   run_wrapper_ex(run_wrapper_ex const&) = delete;
518   run_wrapper_ex(run_wrapper_ex&&) = delete; 518   run_wrapper_ex(run_wrapper_ex&&) = delete;
519   run_wrapper_ex& operator=(run_wrapper_ex const&) = delete; 519   run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
520   run_wrapper_ex& operator=(run_wrapper_ex&&) = delete; 520   run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
521   521  
522 - template<IoRunnable Task> 522 + template<ContextRunnable Task>
HITCBC 523   3 [[nodiscard]] auto operator()(Task t) && 523   3 [[nodiscard]] auto operator()(Task t) &&
524   { 524   {
525   // The task's frame is made; the caller's own frames are not. 525   // The task's frame is made; the caller's own frames are not.
HITCBC 526   3 set_current_frame_allocator(saved_tls_); 526   3 set_current_frame_allocator(saved_tls_);
HITCBC 527   3 restored_ = true; 527   3 restored_ = true;
528   if constexpr (InheritStopToken) 528   if constexpr (InheritStopToken)
529   return run_awaitable_ex<Task, Ex, true, Alloc>{ 529   return run_awaitable_ex<Task, Ex, true, Alloc>{
HITCBC 530   2 std::move(ex_), std::move(alloc_), std::move(t)}; 530   2 std::move(ex_), std::move(alloc_), std::move(t)};
531   else 531   else
532   return run_awaitable_ex<Task, Ex, false, Alloc>{ 532   return run_awaitable_ex<Task, Ex, false, Alloc>{
HITCBC 533   1 std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)}; 533   1 std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
534   } 534   }
535   }; 535   };
536   536  
537   /// Specialization for memory_resource* - stores pointer directly. 537   /// Specialization for memory_resource* - stores pointer directly.
538   template<Executor Ex, bool InheritStopToken> 538   template<Executor Ex, bool InheritStopToken>
539   class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*> 539   class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
540   { 540   {
541   Ex ex_; 541   Ex ex_;
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   std::pmr::memory_resource* saved_tls_ = 545   std::pmr::memory_resource* saved_tls_ =
546   get_current_frame_allocator(); 546   get_current_frame_allocator();
547   bool restored_ = false; 547   bool restored_ = false;
548   548  
549   public: 549   public:
HITCBC 550   4 run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr) 550   4 run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
551   requires InheritStopToken 551   requires InheritStopToken
HITCBC 552   4 : ex_(std::move(ex)) 552   4 : ex_(std::move(ex))
HITCBC 553   4 , mr_(mr) 553   4 , mr_(mr)
554   { 554   {
HITCBC 555   4 set_current_frame_allocator(mr_); 555   4 set_current_frame_allocator(mr_);
HITCBC 556   4 } 556   4 }
557   557  
HITCBC 558   1 run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr) 558   1 run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
559   requires (!InheritStopToken) 559   requires (!InheritStopToken)
HITCBC 560   1 : ex_(std::move(ex)) 560   1 : ex_(std::move(ex))
HITCBC 561   1 , st_(std::move(st)) 561   1 , st_(std::move(st))
HITCBC 562   1 , mr_(mr) 562   1 , mr_(mr)
563   { 563   {
HITCBC 564   1 set_current_frame_allocator(mr_); 564   1 set_current_frame_allocator(mr_);
HITCBC 565   1 } 565   1 }
566   566  
567   // Restores the caller's allocator when evaluating the task argument 567   // Restores the caller's allocator when evaluating the task argument
568   // threw. Not afterwards: a wrapper destroyed with a torn-down parent 568   // threw. Not afterwards: a wrapper destroyed with a torn-down parent
569   // frame must not touch the tearing thread's allocator. 569   // frame must not touch the tearing thread's allocator.
HITCBC 570   5 ~run_wrapper_ex() 570   5 ~run_wrapper_ex()
571   { 571   {
HITCBC 572   5 if(!restored_) 572   5 if(!restored_)
HITCBC 573   1 set_current_frame_allocator(saved_tls_); 573   1 set_current_frame_allocator(saved_tls_);
HITCBC 574   5 } 574   5 }
575   575  
576   // Non-copyable, non-movable (must be used immediately) 576   // Non-copyable, non-movable (must be used immediately)
577   run_wrapper_ex(run_wrapper_ex const&) = delete; 577   run_wrapper_ex(run_wrapper_ex const&) = delete;
578   run_wrapper_ex(run_wrapper_ex&&) = delete; 578   run_wrapper_ex(run_wrapper_ex&&) = delete;
579   run_wrapper_ex& operator=(run_wrapper_ex const&) = delete; 579   run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
580   run_wrapper_ex& operator=(run_wrapper_ex&&) = delete; 580   run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
581   581  
582 - template<IoRunnable Task> 582 + template<ContextRunnable Task>
HITCBC 583   4 [[nodiscard]] auto operator()(Task t) && 583   4 [[nodiscard]] auto operator()(Task t) &&
584   { 584   {
585   // The task's frame is made; the caller's own frames are not. 585   // The task's frame is made; the caller's own frames are not.
HITCBC 586   4 set_current_frame_allocator(saved_tls_); 586   4 set_current_frame_allocator(saved_tls_);
HITCBC 587   4 restored_ = true; 587   4 restored_ = true;
588   if constexpr (InheritStopToken) 588   if constexpr (InheritStopToken)
589   return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{ 589   return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
HITCBC 590   3 std::move(ex_), mr_, std::move(t)}; 590   3 std::move(ex_), mr_, std::move(t)};
591   else 591   else
592   return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{ 592   return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
HITCBC 593   1 std::move(ex_), mr_, std::move(t), std::move(st_)}; 593   1 std::move(ex_), mr_, std::move(t), std::move(st_)};
594   } 594   }
595   }; 595   };
596   596  
597   /// Specialization for no allocator (void). 597   /// Specialization for no allocator (void).
598   template<Executor Ex, bool InheritStopToken> 598   template<Executor Ex, bool InheritStopToken>
599   class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void> 599   class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
600   { 600   {
601   Ex ex_; 601   Ex ex_;
602   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_; 602   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
603   603  
604   public: 604   public:
HITCBC 605   75 explicit run_wrapper_ex(Ex ex) 605   75 explicit run_wrapper_ex(Ex ex)
606   requires InheritStopToken 606   requires InheritStopToken
HITCBC 607   75 : ex_(std::move(ex)) 607   75 : ex_(std::move(ex))
608   { 608   {
HITCBC 609   75 } 609   75 }
610   610  
HITCBC 611   5 run_wrapper_ex(Ex ex, std::stop_token st) 611   5 run_wrapper_ex(Ex ex, std::stop_token st)
612   requires (!InheritStopToken) 612   requires (!InheritStopToken)
HITCBC 613   5 : ex_(std::move(ex)) 613   5 : ex_(std::move(ex))
HITCBC 614   5 , st_(std::move(st)) 614   5 , st_(std::move(st))
615   { 615   {
HITCBC 616   5 } 616   5 }
617   617  
618   // Non-copyable, non-movable (must be used immediately) 618   // Non-copyable, non-movable (must be used immediately)
619   run_wrapper_ex(run_wrapper_ex const&) = delete; 619   run_wrapper_ex(run_wrapper_ex const&) = delete;
620   run_wrapper_ex(run_wrapper_ex&&) = delete; 620   run_wrapper_ex(run_wrapper_ex&&) = delete;
621   run_wrapper_ex& operator=(run_wrapper_ex const&) = delete; 621   run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
622   run_wrapper_ex& operator=(run_wrapper_ex&&) = delete; 622   run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
623   623  
624 - template<IoRunnable Task> 624 + template<ContextRunnable Task>
HITCBC 625   80 [[nodiscard]] auto operator()(Task t) && 625   80 [[nodiscard]] auto operator()(Task t) &&
626   { 626   {
627   if constexpr (InheritStopToken) 627   if constexpr (InheritStopToken)
628   return run_awaitable_ex<Task, Ex, true>{ 628   return run_awaitable_ex<Task, Ex, true>{
HITCBC 629   75 std::move(ex_), std::move(t)}; 629   75 std::move(ex_), std::move(t)};
630   else 630   else
631   return run_awaitable_ex<Task, Ex, false>{ 631   return run_awaitable_ex<Task, Ex, false>{
HITCBC 632   5 std::move(ex_), std::move(t), std::move(st_)}; 632   5 std::move(ex_), std::move(t), std::move(st_)};
633   } 633   }
634   }; 634   };
635   635  
636   /** Wrapper returned by run(st) or run(alloc) that accepts a task. 636   /** Wrapper returned by run(st) or run(alloc) that accepts a task.
637   637  
638   @tparam InheritStopToken If true, inherit caller's stop token. 638   @tparam InheritStopToken If true, inherit caller's stop token.
639   @tparam Alloc The allocator type (void for no allocator). 639   @tparam Alloc The allocator type (void for no allocator).
640   */ 640   */
641   template<bool InheritStopToken, class Alloc> 641   template<bool InheritStopToken, class Alloc>
642   class [[nodiscard]] run_wrapper 642   class [[nodiscard]] run_wrapper
643   { 643   {
644   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_; 644   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
645   frame_memory_resource<Alloc> resource_; 645   frame_memory_resource<Alloc> resource_;
646   Alloc alloc_; // Copy to pass to awaitable 646   Alloc alloc_; // Copy to pass to awaitable
647   647  
648   std::pmr::memory_resource* saved_tls_ = 648   std::pmr::memory_resource* saved_tls_ =
649   get_current_frame_allocator(); 649   get_current_frame_allocator();
650   bool restored_ = false; 650   bool restored_ = false;
651   651  
652   public: 652   public:
HITCBC 653   3 explicit run_wrapper(Alloc alloc) 653   3 explicit run_wrapper(Alloc alloc)
654   requires InheritStopToken 654   requires InheritStopToken
HITCBC 655   3 : resource_(alloc) 655   3 : resource_(alloc)
HITCBC 656   3 , alloc_(std::move(alloc)) 656   3 , alloc_(std::move(alloc))
657   { 657   {
HITCBC 658   3 set_current_frame_allocator(&resource_); 658   3 set_current_frame_allocator(&resource_);
HITCBC 659   3 } 659   3 }
660   660  
HITCBC 661   1 run_wrapper(std::stop_token st, Alloc alloc) 661   1 run_wrapper(std::stop_token st, Alloc alloc)
662   requires (!InheritStopToken) 662   requires (!InheritStopToken)
HITCBC 663   1 : st_(std::move(st)) 663   1 : st_(std::move(st))
HITCBC 664   1 , resource_(alloc) 664   1 , resource_(alloc)
HITCBC 665   1 , alloc_(std::move(alloc)) 665   1 , alloc_(std::move(alloc))
666   { 666   {
HITCBC 667   1 set_current_frame_allocator(&resource_); 667   1 set_current_frame_allocator(&resource_);
HITCBC 668   1 } 668   1 }
669   669  
670   // Restores the caller's allocator when evaluating the task argument 670   // Restores the caller's allocator when evaluating the task argument
671   // threw. Not afterwards: a wrapper destroyed with a torn-down parent 671   // threw. Not afterwards: a wrapper destroyed with a torn-down parent
672   // frame must not touch the tearing thread's allocator. 672   // frame must not touch the tearing thread's allocator.
HITCBC 673   4 ~run_wrapper() 673   4 ~run_wrapper()
674   { 674   {
HITCBC 675   4 if(!restored_) 675   4 if(!restored_)
HITCBC 676   1 set_current_frame_allocator(saved_tls_); 676   1 set_current_frame_allocator(saved_tls_);
HITCBC 677   4 } 677   4 }
678   678  
679   // Non-copyable, non-movable (must be used immediately) 679   // Non-copyable, non-movable (must be used immediately)
680   run_wrapper(run_wrapper const&) = delete; 680   run_wrapper(run_wrapper const&) = delete;
681   run_wrapper(run_wrapper&&) = delete; 681   run_wrapper(run_wrapper&&) = delete;
682   run_wrapper& operator=(run_wrapper const&) = delete; 682   run_wrapper& operator=(run_wrapper const&) = delete;
683   run_wrapper& operator=(run_wrapper&&) = delete; 683   run_wrapper& operator=(run_wrapper&&) = delete;
684   684  
685 - template<IoRunnable Task> 685 + template<ContextRunnable Task>
HITCBC 686   3 [[nodiscard]] auto operator()(Task t) && 686   3 [[nodiscard]] auto operator()(Task t) &&
687   { 687   {
688   // The task's frame is made; the caller's own frames are not. 688   // The task's frame is made; the caller's own frames are not.
HITCBC 689   3 set_current_frame_allocator(saved_tls_); 689   3 set_current_frame_allocator(saved_tls_);
HITCBC 690   3 restored_ = true; 690   3 restored_ = true;
691   if constexpr (InheritStopToken) 691   if constexpr (InheritStopToken)
692   return run_awaitable<Task, true, Alloc>{ 692   return run_awaitable<Task, true, Alloc>{
HITCBC 693   2 std::move(alloc_), std::move(t)}; 693   2 std::move(alloc_), std::move(t)};
694   else 694   else
695   return run_awaitable<Task, false, Alloc>{ 695   return run_awaitable<Task, false, Alloc>{
HITCBC 696   1 std::move(alloc_), std::move(t), std::move(st_)}; 696   1 std::move(alloc_), std::move(t), std::move(st_)};
697   } 697   }
698   }; 698   };
699   699  
700   /// Specialization for memory_resource* - stores pointer directly. 700   /// Specialization for memory_resource* - stores pointer directly.
701   template<bool InheritStopToken> 701   template<bool InheritStopToken>
702   class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*> 702   class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
703   { 703   {
704   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_; 704   std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
705   std::pmr::memory_resource* mr_; 705   std::pmr::memory_resource* mr_;
706   706  
707   std::pmr::memory_resource* saved_tls_ = 707   std::pmr::memory_resource* saved_tls_ =
708   get_current_frame_allocator(); 708   get_current_frame_allocator();
709   bool restored_ = false; 709   bool restored_ = false;
710   710  
711   public: 711   public:
HITCBC 712   13 explicit run_wrapper(std::pmr::memory_resource* mr) 712   13 explicit run_wrapper(std::pmr::memory_resource* mr)
713   requires InheritStopToken 713   requires InheritStopToken
HITCBC 714   13 : mr_(mr) 714   13 : mr_(mr)
715   { 715   {
HITCBC 716   13 set_current_frame_allocator(mr_); 716   13 set_current_frame_allocator(mr_);
HITCBC 717   13 } 717   13 }
718   718  
HITCBC 719   1 run_wrapper(std::stop_token st, std::pmr::memory_resource* mr) 719   1 run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
720   requires (!InheritStopToken) 720   requires (!InheritStopToken)
HITCBC 721   1 : st_(std::move(st)) 721   1 : st_(std::move(st))
HITCBC 722   1 , mr_(mr) 722   1 , mr_(mr)
723   { 723   {
HITCBC 724   1 set_current_frame_allocator(mr_); 724   1 set_current_frame_allocator(mr_);
HITCBC 725   1 } 725   1 }
726   726  
727   // Restores the caller's allocator when evaluating the task argument 727   // Restores the caller's allocator when evaluating the task argument
728   // threw. Not afterwards: a wrapper destroyed with a torn-down parent 728   // threw. Not afterwards: a wrapper destroyed with a torn-down parent
729   // frame must not touch the tearing thread's allocator. 729   // frame must not touch the tearing thread's allocator.
HITCBC 730   14 ~run_wrapper() 730   14 ~run_wrapper()
731   { 731   {
HITCBC 732   14 if(!restored_) 732   14 if(!restored_)
HITCBC 733   1 set_current_frame_allocator(saved_tls_); 733   1 set_current_frame_allocator(saved_tls_);
HITCBC 734   14 } 734   14 }
735   735  
736   // Non-copyable, non-movable (must be used immediately) 736   // Non-copyable, non-movable (must be used immediately)
737   run_wrapper(run_wrapper const&) = delete; 737   run_wrapper(run_wrapper const&) = delete;
738   run_wrapper(run_wrapper&&) = delete; 738   run_wrapper(run_wrapper&&) = delete;
739   run_wrapper& operator=(run_wrapper const&) = delete; 739   run_wrapper& operator=(run_wrapper const&) = delete;
740   run_wrapper& operator=(run_wrapper&&) = delete; 740   run_wrapper& operator=(run_wrapper&&) = delete;
741   741  
742 - template<IoRunnable Task> 742 + template<ContextRunnable Task>
HITCBC 743   13 [[nodiscard]] auto operator()(Task t) && 743   13 [[nodiscard]] auto operator()(Task t) &&
744   { 744   {
745   // The task's frame is made; the caller's own frames are not. 745   // The task's frame is made; the caller's own frames are not.
HITCBC 746   13 set_current_frame_allocator(saved_tls_); 746   13 set_current_frame_allocator(saved_tls_);
HITCBC 747   13 restored_ = true; 747   13 restored_ = true;
748   if constexpr (InheritStopToken) 748   if constexpr (InheritStopToken)
749   return run_awaitable<Task, true, std::pmr::memory_resource*>{ 749   return run_awaitable<Task, true, std::pmr::memory_resource*>{
HITCBC 750   12 mr_, std::move(t)}; 750   12 mr_, std::move(t)};
751   else 751   else
752   return run_awaitable<Task, false, std::pmr::memory_resource*>{ 752   return run_awaitable<Task, false, std::pmr::memory_resource*>{
HITCBC 753   1 mr_, std::move(t), std::move(st_)}; 753   1 mr_, std::move(t), std::move(st_)};
754   } 754   }
755   }; 755   };
756   756  
757   /// Specialization for stop_token only (no allocator). 757   /// Specialization for stop_token only (no allocator).
758   template<> 758   template<>
759   class [[nodiscard]] run_wrapper<false, void> 759   class [[nodiscard]] run_wrapper<false, void>
760   { 760   {
761   std::stop_token st_; 761   std::stop_token st_;
762   762  
763   public: 763   public:
HITCBC 764   2 explicit run_wrapper(std::stop_token st) 764   2 explicit run_wrapper(std::stop_token st)
HITCBC 765   2 : st_(std::move(st)) 765   2 : st_(std::move(st))
766   { 766   {
HITCBC 767   2 } 767   2 }
768   768  
769   // Non-copyable, non-movable (must be used immediately) 769   // Non-copyable, non-movable (must be used immediately)
770   run_wrapper(run_wrapper const&) = delete; 770   run_wrapper(run_wrapper const&) = delete;
771   run_wrapper(run_wrapper&&) = delete; 771   run_wrapper(run_wrapper&&) = delete;
772   run_wrapper& operator=(run_wrapper const&) = delete; 772   run_wrapper& operator=(run_wrapper const&) = delete;
773   run_wrapper& operator=(run_wrapper&&) = delete; 773   run_wrapper& operator=(run_wrapper&&) = delete;
774   774  
775 - template<IoRunnable Task> 775 + template<ContextRunnable Task>
HITCBC 776   2 [[nodiscard]] auto operator()(Task t) && 776   2 [[nodiscard]] auto operator()(Task t) &&
777   { 777   {
HITCBC 778   2 return run_awaitable<Task, false, void>{std::move(t), std::move(st_)}; 778   2 return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
779   } 779   }
780   }; 780   };
781   781  
782   } // namespace boost::capy::detail 782   } // namespace boost::capy::detail
783   783  
784   namespace boost::capy { 784   namespace boost::capy {
785   785  
786   /** Bind a task to execute on a specific executor. 786   /** Bind a task to execute on a specific executor.
787   787  
788   Returns a wrapper that accepts a task and produces an awaitable. 788   Returns a wrapper that accepts a task and produces an awaitable.
789   When co_awaited, the task runs on the specified executor. 789   When co_awaited, the task runs on the specified executor.
790   790  
791   If the inner task is abandoned before it completes, for example 791   If the inner task is abandoned before it completes, for example
792   because its execution context is torn down, the caller's chain is 792   because its execution context is torn down, the caller's chain is
793   abandoned too: it is unwound by its owner, on the owner's executor, 793   abandoned too: it is unwound by its owner, on the owner's executor,
794   without the caller resuming. The awaitable holds work on the 794   without the caller resuming. The awaitable holds work on the
795   caller's executor until the caller resumes or is unwound. 795   caller's executor until the caller resumes or is unwound.
796   796  
797   @par Preconditions 797   @par Preconditions
798   The caller's execution context outlives the inner task and the 798   The caller's execution context outlives the inner task and the
799   unwind of the caller's chain. This is not detected. 799   unwind of the caller's chain. This is not detected.
800   800  
801   @par Example 801   @par Example
802   @par !example example_2 802   @par !example example_2
803   803  
804   804  
805   @param ex The executor on which the task should run. 805   @param ex The executor on which the task should run.
806   806  
807   @return A wrapper that accepts a task for execution. 807   @return A wrapper that accepts a task for execution.
808   808  
809   @see task 809   @see task
810   @see Executor 810   @see Executor
811   */ 811   */
812   template<Executor Ex> 812   template<Executor Ex>
813   [[nodiscard]] auto 813   [[nodiscard]] auto
HITCBC 814   75 run(Ex ex) 814   75 run(Ex ex)
815   { 815   {
HITCBC 816   75 return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)}; 816   75 return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
817   } 817   }
818   818  
819   /** Bind a task to an executor with a stop token. 819   /** Bind a task to an executor with a stop token.
820   820  
821   @param ex The executor on which the task should run. 821   @param ex The executor on which the task should run.
822   @param st The stop token for cooperative cancellation. 822   @param st The stop token for cooperative cancellation.
823   823  
824   @return A wrapper that accepts a task for execution. 824   @return A wrapper that accepts a task for execution.
825   825  
826   @see run(Ex), which describes teardown and its precondition. 826   @see run(Ex), which describes teardown and its precondition.
827   */ 827   */
828   template<Executor Ex> 828   template<Executor Ex>
829   [[nodiscard]] auto 829   [[nodiscard]] auto
HITCBC 830   5 run(Ex ex, std::stop_token st) 830   5 run(Ex ex, std::stop_token st)
831   { 831   {
832   return detail::run_wrapper_ex<Ex, false, void>{ 832   return detail::run_wrapper_ex<Ex, false, void>{
HITCBC 833   5 std::move(ex), std::move(st)}; 833   5 std::move(ex), std::move(st)};
834   } 834   }
835   835  
836   /** Bind a task to an executor with a memory resource. 836   /** Bind a task to an executor with a memory resource.
837   837  
838   @param ex The executor on which the task should run. 838   @param ex The executor on which the task should run.
839   @param mr The memory resource for frame allocation. 839   @param mr The memory resource for frame allocation.
840   840  
841   @return A wrapper that accepts a task for execution. 841   @return A wrapper that accepts a task for execution.
842   842  
843   @see run(Ex), which describes teardown and its precondition. 843   @see run(Ex), which describes teardown and its precondition.
844   */ 844   */
845   template<Executor Ex> 845   template<Executor Ex>
846   [[nodiscard]] auto 846   [[nodiscard]] auto
HITCBC 847   4 run(Ex ex, std::pmr::memory_resource* mr) 847   4 run(Ex ex, std::pmr::memory_resource* mr)
848   { 848   {
849   return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{ 849   return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
HITCBC 850   4 std::move(ex), mr}; 850   4 std::move(ex), mr};
851   } 851   }
852   852  
853   /** Bind a task to an executor with a standard allocator. 853   /** Bind a task to an executor with a standard allocator.
854   854  
855   Await the awaitable in the expression that creates it, as in 855   Await the awaitable in the expression that creates it, as in
856   `co_await run(ex, alloc)(task())`: the task's frame is allocated from 856   `co_await run(ex, alloc)(task())`: the task's frame is allocated from
857   memory the wrapper holds until the end of that expression. 857   memory the wrapper holds until the end of that expression.
858   858  
859   @param ex The executor on which the task should run. 859   @param ex The executor on which the task should run.
860   @param alloc The allocator for frame allocation. 860   @param alloc The allocator for frame allocation.
861   861  
862   @return A wrapper that accepts a task for execution. 862   @return A wrapper that accepts a task for execution.
863   863  
864   @see run(Ex), which describes teardown and its precondition. 864   @see run(Ex), which describes teardown and its precondition.
865   */ 865   */
866   template<Executor Ex, detail::Allocator Alloc> 866   template<Executor Ex, detail::Allocator Alloc>
867   [[nodiscard]] auto 867   [[nodiscard]] auto
HITCBC 868   3 run(Ex ex, Alloc alloc) 868   3 run(Ex ex, Alloc alloc)
869   { 869   {
870   return detail::run_wrapper_ex<Ex, true, Alloc>{ 870   return detail::run_wrapper_ex<Ex, true, Alloc>{
HITCBC 871   3 std::move(ex), std::move(alloc)}; 871   3 std::move(ex), std::move(alloc)};
872   } 872   }
873   873  
874   /** Bind a task to an executor with stop token and memory resource. 874   /** Bind a task to an executor with stop token and memory resource.
875   875  
876   @param ex The executor on which the task should run. 876   @param ex The executor on which the task should run.
877   @param st The stop token for cooperative cancellation. 877   @param st The stop token for cooperative cancellation.
878   @param mr The memory resource for frame allocation. 878   @param mr The memory resource for frame allocation.
879   879  
880   @return A wrapper that accepts a task for execution. 880   @return A wrapper that accepts a task for execution.
881   881  
882   @see run(Ex), which describes teardown and its precondition. 882   @see run(Ex), which describes teardown and its precondition.
883   */ 883   */
884   template<Executor Ex> 884   template<Executor Ex>
885   [[nodiscard]] auto 885   [[nodiscard]] auto
HITCBC 886   1 run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr) 886   1 run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
887   { 887   {
888   return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{ 888   return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
HITCBC 889   1 std::move(ex), std::move(st), mr}; 889   1 std::move(ex), std::move(st), mr};
890   } 890   }
891   891  
892   /** Bind a task to an executor with stop token and standard allocator. 892   /** Bind a task to an executor with stop token and standard allocator.
893   893  
894   Await the awaitable in the expression that creates it, as in 894   Await the awaitable in the expression that creates it, as in
895   `co_await run(ex, alloc)(task())`: the task's frame is allocated from 895   `co_await run(ex, alloc)(task())`: the task's frame is allocated from
896   memory the wrapper holds until the end of that expression. 896   memory the wrapper holds until the end of that expression.
897   897  
898   @param ex The executor on which the task should run. 898   @param ex The executor on which the task should run.
899   @param st The stop token for cooperative cancellation. 899   @param st The stop token for cooperative cancellation.
900   @param alloc The allocator for frame allocation. 900   @param alloc The allocator for frame allocation.
901   901  
902   @return A wrapper that accepts a task for execution. 902   @return A wrapper that accepts a task for execution.
903   903  
904   @see run(Ex), which describes teardown and its precondition. 904   @see run(Ex), which describes teardown and its precondition.
905   */ 905   */
906   template<Executor Ex, detail::Allocator Alloc> 906   template<Executor Ex, detail::Allocator Alloc>
907   [[nodiscard]] auto 907   [[nodiscard]] auto
HITCBC 908   1 run(Ex ex, std::stop_token st, Alloc alloc) 908   1 run(Ex ex, std::stop_token st, Alloc alloc)
909   { 909   {
910   return detail::run_wrapper_ex<Ex, false, Alloc>{ 910   return detail::run_wrapper_ex<Ex, false, Alloc>{
HITCBC 911   1 std::move(ex), std::move(st), std::move(alloc)}; 911   1 std::move(ex), std::move(st), std::move(alloc)};
912   } 912   }
913   913  
914   /** Run a task with a custom stop token. 914   /** Run a task with a custom stop token.
915   915  
916   The task inherits the caller's executor. Only the stop token 916   The task inherits the caller's executor. Only the stop token
917   is overridden. 917   is overridden.
918   918  
919   @par Example 919   @par Example
920   @par !example example_1 920   @par !example example_1
921   921  
922   922  
923   @param st The stop token for cooperative cancellation. 923   @param st The stop token for cooperative cancellation.
924   924  
925   @return A wrapper that accepts a task for execution. 925   @return A wrapper that accepts a task for execution.
926   */ 926   */
927   [[nodiscard]] inline auto 927   [[nodiscard]] inline auto
HITCBC 928   2 run(std::stop_token st) 928   2 run(std::stop_token st)
929   { 929   {
HITCBC 930   2 return detail::run_wrapper<false, void>{std::move(st)}; 930   2 return detail::run_wrapper<false, void>{std::move(st)};
931   } 931   }
932   932  
933   /** Run a task with a custom memory resource. 933   /** Run a task with a custom memory resource.
934   934  
935   The task inherits the caller's executor. The memory resource 935   The task inherits the caller's executor. The memory resource
936   is used for nested frame allocations. 936   is used for nested frame allocations.
937   937  
938   @param mr The memory resource for frame allocation. 938   @param mr The memory resource for frame allocation.
939   939  
940   @return A wrapper that accepts a task for execution. 940   @return A wrapper that accepts a task for execution.
941   */ 941   */
942   [[nodiscard]] inline auto 942   [[nodiscard]] inline auto
HITCBC 943   13 run(std::pmr::memory_resource* mr) 943   13 run(std::pmr::memory_resource* mr)
944   { 944   {
HITCBC 945   13 return detail::run_wrapper<true, std::pmr::memory_resource*>{mr}; 945   13 return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
946   } 946   }
947   947  
948   // MrDocs 0.8.0 reports `run: Documented parameter 'alloc' does not exist` and 948   // MrDocs 0.8.0 reports `run: Documented parameter 'alloc' does not exist` and
949   // pins it to the run(Ex) overload near the top of this namespace: 949   // pins it to the run(Ex) overload near the top of this namespace:
950   // `detail::Allocator` is implementation-defined, so the constraint is erased 950   // `detail::Allocator` is implementation-defined, so the constraint is erased
951   // and the two single-parameter `run` overloads collapse onto one page. The 951   // and the two single-parameter `run` overloads collapse onto one page. The
952   // finding is grandfathered in doc/lint/baseline.json. Deleting the `@param 952   // finding is grandfathered in doc/lint/baseline.json. Deleting the `@param
953   // alloc` below clears it and produces no replacement finding, because MrDocs 953   // alloc` below clears it and produces no replacement finding, because MrDocs
954   // has no model of this overload at all -- so it would silently drop real 954   // has no model of this overload at all -- so it would silently drop real
955   // documentation. Keep the `@param`. 955   // documentation. Keep the `@param`.
956   /** Run a task with a custom standard allocator. 956   /** Run a task with a custom standard allocator.
957   957  
958   Await the awaitable in the expression that creates it, as in 958   Await the awaitable in the expression that creates it, as in
959   `co_await run(alloc)(task())`: the task's frame is allocated from 959   `co_await run(alloc)(task())`: the task's frame is allocated from
960   memory the wrapper holds until the end of that expression. 960   memory the wrapper holds until the end of that expression.
961   961  
962   The task inherits the caller's executor. The allocator is used 962   The task inherits the caller's executor. The allocator is used
963   for nested frame allocations. 963   for nested frame allocations.
964   964  
965   @param alloc The allocator for frame allocation. 965   @param alloc The allocator for frame allocation.
966   966  
967   @return A wrapper that accepts a task for execution. 967   @return A wrapper that accepts a task for execution.
968   */ 968   */
969   template<detail::Allocator Alloc> 969   template<detail::Allocator Alloc>
970   [[nodiscard]] auto 970   [[nodiscard]] auto
HITCBC 971   3 run(Alloc alloc) 971   3 run(Alloc alloc)
972   { 972   {
HITCBC 973   3 return detail::run_wrapper<true, Alloc>{std::move(alloc)}; 973   3 return detail::run_wrapper<true, Alloc>{std::move(alloc)};
974   } 974   }
975   975  
976   /** Run a task with stop token and memory resource. 976   /** Run a task with stop token and memory resource.
977   977  
978   The task inherits the caller's executor. 978   The task inherits the caller's executor.
979   979  
980   @param st The stop token for cooperative cancellation. 980   @param st The stop token for cooperative cancellation.
981   @param mr The memory resource for frame allocation. 981   @param mr The memory resource for frame allocation.
982   982  
983   @return A wrapper that accepts a task for execution. 983   @return A wrapper that accepts a task for execution.
984   */ 984   */
985   [[nodiscard]] inline auto 985   [[nodiscard]] inline auto
HITCBC 986   1 run(std::stop_token st, std::pmr::memory_resource* mr) 986   1 run(std::stop_token st, std::pmr::memory_resource* mr)
987   { 987   {
988   return detail::run_wrapper<false, std::pmr::memory_resource*>{ 988   return detail::run_wrapper<false, std::pmr::memory_resource*>{
HITCBC 989   1 std::move(st), mr}; 989   1 std::move(st), mr};
990   } 990   }
991   991  
992   /** Run a task with stop token and standard allocator. 992   /** Run a task with stop token and standard allocator.
993   993  
994   Await the awaitable in the expression that creates it, as in 994   Await the awaitable in the expression that creates it, as in
995   `co_await run(alloc)(task())`: the task's frame is allocated from 995   `co_await run(alloc)(task())`: the task's frame is allocated from
996   memory the wrapper holds until the end of that expression. 996   memory the wrapper holds until the end of that expression.
997   997  
998   The task inherits the caller's executor. 998   The task inherits the caller's executor.
999   999  
1000   @param st The stop token for cooperative cancellation. 1000   @param st The stop token for cooperative cancellation.
1001   @param alloc The allocator for frame allocation. 1001   @param alloc The allocator for frame allocation.
1002   1002  
1003   @return A wrapper that accepts a task for execution. 1003   @return A wrapper that accepts a task for execution.
1004   */ 1004   */
1005   template<detail::Allocator Alloc> 1005   template<detail::Allocator Alloc>
1006   [[nodiscard]] auto 1006   [[nodiscard]] auto
HITCBC 1007   1 run(std::stop_token st, Alloc alloc) 1007   1 run(std::stop_token st, Alloc alloc)
1008   { 1008   {
1009   return detail::run_wrapper<false, Alloc>{ 1009   return detail::run_wrapper<false, Alloc>{
HITCBC 1010   1 std::move(st), std::move(alloc)}; 1010   1 std::move(st), std::move(alloc)};
1011   } 1011   }
1012   1012  
1013   } // namespace boost::capy 1013   } // namespace boost::capy
1014   1014  
1015   #endif 1015   #endif