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