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