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