100.00% Lines (95/95) 100.00% Functions (30/30)
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_TASK_HPP 11   #ifndef BOOST_CAPY_TASK_HPP
12   #define BOOST_CAPY_TASK_HPP 12   #define BOOST_CAPY_TASK_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/concept/executor.hpp> 15   #include <boost/capy/concept/executor.hpp>
16   #include <boost/capy/concept/io_awaitable.hpp> 16   #include <boost/capy/concept/io_awaitable.hpp>
17   #include <boost/capy/ex/io_awaitable_promise_base.hpp> 17   #include <boost/capy/ex/io_awaitable_promise_base.hpp>
18   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/ex/frame_allocator.hpp> 19   #include <boost/capy/ex/frame_allocator.hpp>
20   #include <boost/capy/detail/await_suspend_helper.hpp> 20   #include <boost/capy/detail/await_suspend_helper.hpp>
21   #include <boost/capy/io_result.hpp> 21   #include <boost/capy/io_result.hpp>
22   22  
23   #include <exception> 23   #include <exception>
24   #include <optional> 24   #include <optional>
25   #include <type_traits> 25   #include <type_traits>
26   #include <utility> 26   #include <utility>
27   #include <variant> 27   #include <variant>
28   28  
29   namespace boost { 29   namespace boost {
30   namespace capy { 30   namespace capy {
31   31  
32   namespace detail { 32   namespace detail {
33   33  
34   // Helper base for result storage and return_void/return_value 34   // Helper base for result storage and return_void/return_value
35   template<typename T> 35   template<typename T>
36   struct task_return_base 36   struct task_return_base
37   { 37   {
38   std::optional<T> result_; 38   std::optional<T> result_;
39   39  
HITCBC 40   862 void return_value(T value) 40   889 void return_value(T value)
41   { 41   {
HITCBC 42   862 result_ = std::move(value); 42   889 result_ = std::move(value);
HITCBC 43   862 } 43   889 }
44   44  
HITCBC 45   273 T&& result() noexcept 45   279 T&& result() noexcept
46   { 46   {
HITCBC 47   273 return std::move(*result_); 47   279 return std::move(*result_);
48   } 48   }
49   }; 49   };
50   50  
51   template<> 51   template<>
52   struct task_return_base<void> 52   struct task_return_base<void>
53   { 53   {
HITCBC 54   1234 void return_void() 54   3667 void return_void()
55   { 55   {
HITCBC 56   1234 } 56   3667 }
57   }; 57   };
58   58  
59   } // namespace detail 59   } // namespace detail
60   60  
61   /** Defers a coroutine body until awaited, then runs it inline on the caller's thread. 61   /** Defers a coroutine body until awaited, then runs it inline on the caller's thread.
62   62  
63   Use `task<T>` as the return type for coroutines that perform I/O 63   Use `task<T>` as the return type for coroutines that perform I/O
64   and return a value of type `T`. The coroutine body does not start 64   and return a value of type `T`. The coroutine body does not start
65   executing until the task is awaited, enabling efficient composition 65   executing until the task is awaited, enabling efficient composition
66   without unnecessary eager execution. 66   without unnecessary eager execution.
67   67  
68   The task participates in the I/O awaitable protocol: when awaited, 68   The task participates in the I/O awaitable protocol: when awaited,
69   it receives the caller's executor and stop token, propagating them 69   it receives the caller's executor and stop token, propagating them
70   to nested `co_await` expressions. This enables cancellation and 70   to nested `co_await` expressions. This enables cancellation and
71   proper completion dispatch across executor boundaries. 71   proper completion dispatch across executor boundaries.
72   72  
73   @par Await-effects 73   @par Await-effects
74   74  
75   Let `t` be a `task<T>`. `co_await t` always suspends the awaiting 75   Let `t` be a `task<T>`. `co_await t` always suspends the awaiting
76   coroutine, then transfers control directly into the task's coroutine 76   coroutine, then transfers control directly into the task's coroutine
77   body on the current thread; no executor operation is posted. The task 77   body on the current thread; no executor operation is posted. The task
78   records the caller's environment (executor, stop token, and frame 78   records the caller's environment (executor, stop token, and frame
79   allocator) by pointer rather than copying it. It propagates that 79   allocator) by pointer rather than copying it. It propagates that
80   environment to every `co_await` inside the body. 80   environment to every `co_await` inside the body.
81   81  
82   The body runs until it returns or exits via an exception. Control 82   The body runs until it returns or exits via an exception. Control
83   then transfers directly back to the awaiting coroutine, again 83   then transfers directly back to the awaiting coroutine, again
84   without an executor operation. 84   without an executor operation.
85   85  
86   `task` never inspects the stop token; it only propagates it. A task 86   `task` never inspects the stop token; it only propagates it. A task
87   body observes a stop request through the results of the operations it 87   body observes a stop request through the results of the operations it
88   awaits, or by reading the token itself. See @ref quitter for a task 88   awaits, or by reading the token itself. See @ref quitter for a task
89   that stops its own body. 89   that stops its own body.
90   90  
91   @par Await-returns 91   @par Await-returns
92   The value the body passed to `co_return`, moved out of the task, or 92   The value the body passed to `co_return`, moved out of the task, or
93   nothing when `T` is `void`. 93   nothing when `T` is `void`.
94   94  
95   If the body exits via an unhandled exception, that exception is 95   If the body exits via an unhandled exception, that exception is
96   rethrown instead. 96   rethrown instead.
97   97  
98   @par Await-postcondition 98   @par Await-postcondition
99   The task's coroutine has run to completion and is suspended at its 99   The task's coroutine has run to completion and is suspended at its
100   final suspend point. The task still owns the frame, but not the 100   final suspend point. The task still owns the frame, but not the
101   result: the await moves it out, so a task must not be awaited twice. 101   result: the await moves it out, so a task must not be awaited twice.
102   102  
103   @par Thread Safety 103   @par Thread Safety
104   Distinct objects: Safe. 104   Distinct objects: Safe.
105   Shared objects: Unsafe. 105   Shared objects: Unsafe.
106   106  
107   @par Example 107   @par Example
108   108  
109   @par !example example 109   @par !example example
110   110  
111   111  
112   @tparam T The result type. Use `task<>` for `task<void>`. 112   @tparam T The result type. Use `task<>` for `task<void>`.
113   113  
114   @see IoRunnable, IoAwaitable, run, run_async 114   @see IoRunnable, IoAwaitable, run, run_async
115   */ 115   */
116   template<typename T = void> 116   template<typename T = void>
117   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 117   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
118   task 118   task
119   { 119   {
120   /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `io_awaitable_promise_base`. 120   /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `io_awaitable_promise_base`.
121   121  
122   This is the promise object the compiler associates with a 122   This is the promise object the compiler associates with a
123   `task<T>` coroutine. It satisfies the coroutine promise 123   `task<T>` coroutine. It satisfies the coroutine promise
124   requirements and participates in the I/O awaitable protocol via 124   requirements and participates in the I/O awaitable protocol via
125   @ref io_awaitable_promise_base. It is part of the coroutine 125   @ref io_awaitable_promise_base. It is part of the coroutine
126   machinery and is not intended to be used directly by callers. 126   machinery and is not intended to be used directly by callers.
127   127  
128   Result storage and `return_value`/`return_void` are provided by 128   Result storage and `return_value`/`return_void` are provided by
129   `detail::task_return_base<T>`. 129   `detail::task_return_base<T>`.
130   130  
131   @see io_awaitable_promise_base, IoRunnable 131   @see io_awaitable_promise_base, IoRunnable
132   */ 132   */
133   struct promise_type 133   struct promise_type
134   : io_awaitable_promise_base<promise_type> 134   : io_awaitable_promise_base<promise_type>
135   , detail::task_return_base<T> 135   , detail::task_return_base<T>
136   { 136   {
137   private: 137   private:
138   friend task; 138   friend task;
139   union { std::exception_ptr ep_; }; 139   union { std::exception_ptr ep_; };
140   bool has_ep_; 140   bool has_ep_;
141   141  
142   public: 142   public:
143   /// Construct the promise with no stored exception. 143   /// Construct the promise with no stored exception.
HITCBC 144   2744 promise_type() noexcept 144   112709 promise_type() noexcept
HITCBC 145   2744 : has_ep_(false) 145   112709 : has_ep_(false)
146   { 146   {
HITCBC 147   2744 } 147   112709 }
148   148  
149   /// Destroy the promise, releasing any stored exception. 149   /// Destroy the promise, releasing any stored exception.
HITCBC 150   2744 ~promise_type() 150   112709 ~promise_type()
151   { 151   {
HITCBC 152   2744 if(has_ep_) 152   112709 if(has_ep_)
HITCBC 153   489 ep_.~exception_ptr(); 153   490 ep_.~exception_ptr();
HITCBC 154   2744 } 154   112709 }
155   155  
156   /** Return the exception captured by the coroutine body, if any. 156   /** Return the exception captured by the coroutine body, if any.
157   157  
158   @return The stored exception, or a null `std::exception_ptr` 158   @return The stored exception, or a null `std::exception_ptr`
159   if the coroutine did not exit via an unhandled exception. 159   if the coroutine did not exit via an unhandled exception.
160   */ 160   */
HITCBC 161   2139 std::exception_ptr exception() const noexcept 161   4545 std::exception_ptr exception() const noexcept
162   { 162   {
HITCBC 163   2139 if(has_ep_) 163   4545 if(has_ep_)
HITCBC 164   730 return ep_; 164   732 return ep_;
HITCBC 165   1409 return {}; 165   3813 return {};
166   } 166   }
167   167  
168   /** Return the owning `task` for this coroutine. 168   /** Return the owning `task` for this coroutine.
169   169  
170   Called by the compiler to produce the object returned to the 170   Called by the compiler to produce the object returned to the
171   caller when the coroutine is created. 171   caller when the coroutine is created.
172   172  
173   @return A `task` owning the coroutine frame. 173   @return A `task` owning the coroutine frame.
174   */ 174   */
HITCBC 175   2744 task get_return_object() 175   112709 task get_return_object()
176   { 176   {
HITCBC 177   2744 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 177   112709 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
178   } 178   }
179   179  
180   /** Return the initial-suspend awaiter. 180   /** Return the initial-suspend awaiter.
181   181  
182   The coroutine always suspends at the initial suspend point, 182   The coroutine always suspends at the initial suspend point,
183   so the body does not start until the task is awaited. When the 183   so the body does not start until the task is awaited. When the
184   body is resumed, the awaiter restores the thread-local frame 184   body is resumed, the awaiter restores the thread-local frame
185   allocator from the stored environment. 185   allocator from the stored environment.
186   186  
187   @return An awaiter that suspends unconditionally. 187   @return An awaiter that suspends unconditionally.
188   */ 188   */
HITCBC 189   2744 auto initial_suspend() noexcept 189   112709 auto initial_suspend() noexcept
190   { 190   {
191   struct awaiter 191   struct awaiter
192   { 192   {
193   promise_type* p_; 193   promise_type* p_;
194   194  
HITCBC 195   2744 bool await_ready() const noexcept 195   112709 bool await_ready() const noexcept
196   { 196   {
HITCBC 197   2744 return false; 197   112709 return false;
198   } 198   }
199   199  
HITCBC 200   2744 void await_suspend(std::coroutine_handle<>) const noexcept 200   112709 void await_suspend(std::coroutine_handle<>) const noexcept
201   { 201   {
HITCBC 202   2744 } 202   112709 }
203   203  
HITCBC 204   2740 void await_resume() const noexcept 204   107660 void await_resume() const noexcept
205   { 205   {
206   // Restore TLS when body starts executing 206   // Restore TLS when body starts executing
HITCBC 207   2740 set_current_frame_allocator(p_->environment()->frame_allocator); 207   107660 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 208   2740 } 208   107660 }
209   }; 209   };
HITCBC 210   2744 return awaiter{this}; 210   112709 return awaiter{this};
211   } 211   }
212   212  
213   /** Return the final-suspend awaiter. 213   /** Return the final-suspend awaiter.
214   214  
215   The coroutine always suspends at the final suspend point. The 215   The coroutine always suspends at the final suspend point. The
216   awaiter's `await_suspend` performs symmetric transfer to the 216   awaiter's `await_suspend` performs symmetric transfer to the
217   stored continuation (consuming it), resuming the awaiting 217   stored continuation (consuming it), resuming the awaiting
218   coroutine. 218   coroutine.
219   219  
220   @return An awaiter that suspends and transfers to the 220   @return An awaiter that suspends and transfers to the
221   continuation. 221   continuation.
222   */ 222   */
HITCBC 223   2585 auto final_suspend() noexcept 223   5046 auto final_suspend() noexcept
224   { 224   {
225   struct awaiter 225   struct awaiter
226   { 226   {
227   promise_type* p_; 227   promise_type* p_;
228   228  
HITCBC 229   2585 bool await_ready() const noexcept 229   5046 bool await_ready() const noexcept
230   { 230   {
HITCBC 231   2585 return false; 231   5046 return false;
232   } 232   }
233   233  
HITCBC 234   2585 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 234   5046 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
235   { 235   {
HITCBC 236   2585 return p_->continuation(); 236   5046 return p_->continuation();
237   } 237   }
238   238  
239   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 239   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
240   }; 240   };
HITCBC 241   2585 return awaiter{this}; 241   5046 return awaiter{this};
242   } 242   }
243   243  
244   /** Capture the in-flight exception from the coroutine body. 244   /** Capture the in-flight exception from the coroutine body.
245   245  
246   Called by the compiler when the coroutine body exits via an 246   Called by the compiler when the coroutine body exits via an
247   unhandled exception. The captured exception is rethrown when 247   unhandled exception. The captured exception is rethrown when
248   the task is awaited. 248   the task is awaited.
249   */ 249   */
HITCBC 250   489 void unhandled_exception() noexcept 250   490 void unhandled_exception() noexcept
251   { 251   {
HITCBC 252   489 new (&ep_) std::exception_ptr(std::current_exception()); 252   490 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 253   489 has_ep_ = true; 253   490 has_ep_ = true;
HITCBC 254   489 } 254   490 }
255   255  
256   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable. 256   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
257   257  
258   Forwards the environment to the inner awaitable's 258   Forwards the environment to the inner awaitable's
259   environment-taking `await_suspend` and restores the 259   environment-taking `await_suspend` and restores the
260   thread-local frame allocator before the body resumes. 260   thread-local frame allocator before the body resumes.
261   261  
262   @tparam Awaitable The awaitable being transformed. 262   @tparam Awaitable The awaitable being transformed.
263   */ 263   */
264   template<class Awaitable> 264   template<class Awaitable>
265   struct transform_awaiter 265   struct transform_awaiter
266   { 266   {
267   /// The wrapped awaitable, decayed and stored by value. 267   /// The wrapped awaitable, decayed and stored by value.
268   std::decay_t<Awaitable> a_; 268   std::decay_t<Awaitable> a_;
269   269  
270   /// The promise of the coroutine performing the `co_await`. 270   /// The promise of the coroutine performing the `co_await`.
271   promise_type* p_; 271   promise_type* p_;
272   272  
273   /** Report whether the wrapped awaitable is already complete. 273   /** Report whether the wrapped awaitable is already complete.
274   274  
275   @return The wrapped awaitable's own `await_ready` result: 275   @return The wrapped awaitable's own `await_ready` result:
276   `true` if no suspension is needed. 276   `true` if no suspension is needed.
277   */ 277   */
HITCBC 278   2846 bool await_ready() noexcept 278   107731 bool await_ready() noexcept
279   { 279   {
HITCBC 280   2846 return a_.await_ready(); 280   107731 return a_.await_ready();
281   } 281   }
282   282  
283   /** Restore the frame allocator, then resume the wrapped 283   /** Restore the frame allocator, then resume the wrapped
284   awaitable. 284   awaitable.
285   285  
286   Reinstalls the thread-local frame allocator from the stored 286   Reinstalls the thread-local frame allocator from the stored
287   environment before the body continues. This is needed 287   environment before the body continues. This is needed
288   because the resumption may arrive on a different thread 288   because the resumption may arrive on a different thread
289   than the one that suspended. 289   than the one that suspended.
290   290  
291   @return The wrapped awaitable's await-result, forwarded 291   @return The wrapped awaitable's await-result, forwarded
292   unchanged. 292   unchanged.
293   */ 293   */
HITCBC 294   2691 decltype(auto) await_resume() 294   5117 decltype(auto) await_resume()
295   { 295   {
296   // Restore TLS before body resumes 296   // Restore TLS before body resumes
HITCBC 297   2691 set_current_frame_allocator(p_->environment()->frame_allocator); 297   5117 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 298   2691 return a_.await_resume(); 298   5117 return a_.await_resume();
299   } 299   }
300   300  
301   /** Suspend by calling the wrapped awaitable with the 301   /** Suspend by calling the wrapped awaitable with the
302   environment. 302   environment.
303   303  
304   This is the plain `await_suspend` the compiler calls for the 304   This is the plain `await_suspend` the compiler calls for the
305   nested `co_await`. It forwards to the wrapped awaitable's 305   nested `co_await`. It forwards to the wrapped awaitable's
306   @ref IoAwaitable overload, supplying the promise's stored 306   @ref IoAwaitable overload, supplying the promise's stored
307   environment as the second argument. It then hands back 307   environment as the second argument. It then hands back
308   that call's result unchanged, so the wrapped awaitable's 308   that call's result unchanged, so the wrapped awaitable's
309   suspension decision, whatever form it takes, is preserved. 309   suspension decision, whatever form it takes, is preserved.
310   310  
311   @param h The coroutine performing the `co_await`. 311   @param h The coroutine performing the `co_await`.
312   312  
313   @return Whatever the wrapped awaitable's `await_suspend` 313   @return Whatever the wrapped awaitable's `await_suspend`
314   returns. When that is a `std::coroutine_handle<>`, the 314   returns. When that is a `std::coroutine_handle<>`, the
315   handle is routed through `detail::symmetric_transfer`. 315   handle is routed through `detail::symmetric_transfer`.
316   On MSVC that helper resumes the handle on the current 316   On MSVC that helper resumes the handle on the current
317   stack, and this function returns `void`, so the awaiting 317   stack, and this function returns `void`, so the awaiting
318   coroutine suspends unconditionally. On every other 318   coroutine suspends unconditionally. On every other
319   compiler the handle is returned unchanged for symmetric 319   compiler the handle is returned unchanged for symmetric
320   transfer. 320   transfer.
321   */ 321   */
322   template<class Promise> 322   template<class Promise>
HITCBC 323   2238 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 323   107097 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
324   { 324   {
325   using R = decltype(a_.await_suspend(h, p_->environment())); 325   using R = decltype(a_.await_suspend(h, p_->environment()));
326   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 326   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 327   1239 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 327   106066 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
328   else 328   else
HITCBC 329   999 return a_.await_suspend(h, p_->environment()); 329   1031 return a_.await_suspend(h, p_->environment());
330   } 330   }
331   }; 331   };
332   332  
333   /** Transform a nested awaitable before `co_await`. 333   /** Transform a nested awaitable before `co_await`.
334   334  
335   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the 335   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
336   coroutine's environment is propagated into it. A diagnostic 336   coroutine's environment is propagated into it. A diagnostic
337   is emitted if the awaitable does not satisfy @ref IoAwaitable. 337   is emitted if the awaitable does not satisfy @ref IoAwaitable.
338   338  
339   @param a The awaitable expression from `co_await a`. 339   @param a The awaitable expression from `co_await a`.
340   340  
341   @return A @ref transform_awaiter wrapping `a`. 341   @return A @ref transform_awaiter wrapping `a`.
342   */ 342   */
343   template<class Awaitable> 343   template<class Awaitable>
HITCBC 344   2846 auto transform_awaitable(Awaitable&& a) 344   107731 auto transform_awaitable(Awaitable&& a)
345   { 345   {
346   using A = std::decay_t<Awaitable>; 346   using A = std::decay_t<Awaitable>;
347   if constexpr (IoAwaitable<A>) 347   if constexpr (IoAwaitable<A>)
348   { 348   {
349   return transform_awaiter<Awaitable>{ 349   return transform_awaiter<Awaitable>{
HITCBC 350   4347 std::forward<Awaitable>(a), this}; 350   212951 std::forward<Awaitable>(a), this};
351   } 351   }
352   else 352   else
353   { 353   {
354   static_assert(IoAwaitable<A>, "requires IoAwaitable"); 354   static_assert(IoAwaitable<A>, "requires IoAwaitable");
355   } 355   }
HITCBC 356   1501 } 356   105220 }
357   }; 357   };
358   358  
359   /** Handle to the owned coroutine frame. 359   /** Handle to the owned coroutine frame.
360   360  
361   Null when the task is empty (for example after a move or after 361   Null when the task is empty (for example after a move or after
362   @ref release). Prefer @ref handle to read this; the member is 362   @ref release). Prefer @ref handle to read this; the member is
363   public for use by the coroutine machinery. 363   public for use by the coroutine machinery.
364   */ 364   */
365   std::coroutine_handle<promise_type> h_; 365   std::coroutine_handle<promise_type> h_;
366   366  
367 - /// Destroy the task and its coroutine frame if owned. 367 + /** Destroy the task and its coroutine frame if owned.
  368 +
  369 + @par Preconditions
  370 + The frame is not lent: no parent is suspended awaiting it,
  371 + unless its chain is being unwound after an @ref abandon.
  372 + Destroying a lent frame otherwise calls `std::terminate`; end
  373 + a running chain by requesting stop, or by destroying its
  374 + execution context.
  375 + */
HITCBC 368   5812 ~task() 376   116779 ~task()
369   { 377   {
HITCBC 370 - 5812 if(h_) 378 + 116779 destroy_frame();
DCB 371 - 754 h_.destroy();  
HITCBC 372   5812 } 379   116779 }
373   380  
374   /** Report whether the awaited task is already complete. 381   /** Report whether the awaited task is already complete.
375   382  
376   Always returns `false`; a task is lazy and has not started when 383   Always returns `false`; a task is lazy and has not started when
377   it is awaited, so the awaiting coroutine always suspends. 384   it is awaited, so the awaiting coroutine always suspends.
378   385  
379   @return `false`. 386   @return `false`.
380   */ 387   */
HITCBC 381   751 bool await_ready() const noexcept 388   2104 bool await_ready() const noexcept
382   { 389   {
HITCBC 383   751 return false; 390   2104 return false;
384   } 391   }
385   392  
386   /** Return the task's result, rethrowing any captured exception. 393   /** Return the task's result, rethrowing any captured exception.
387   394  
388   If the coroutine body exited via an unhandled exception, that 395   If the coroutine body exited via an unhandled exception, that
389   exception is rethrown here. Otherwise the result is returned by 396   exception is rethrown here. Otherwise the result is returned by
390   move (for `task<T>`) or nothing is returned (for `task<void>`). 397   move (for `task<T>`) or nothing is returned (for `task<void>`).
391   398  
392   @return The result value for non-void `T`; otherwise `void`. 399   @return The result value for non-void `T`; otherwise `void`.
393   400  
394   @throws The exception captured by the coroutine body, if any. 401   @throws The exception captured by the coroutine body, if any.
395   402  
396   @note Discarding an `io_result` silently drops the error 403   @note Discarding an `io_result` silently drops the error
397   code, so that overload is marked `[[nodiscard]]`. 404   code, so that overload is marked `[[nodiscard]]`.
398   */ 405   */
HITCBC 399   545 [[nodiscard]] auto await_resume() 406   562 [[nodiscard]] auto await_resume()
400   requires detail::is_io_result_v<T> 407   requires detail::is_io_result_v<T>
401   { 408   {
HITGNC   409 + 562 lent_ = false;
HITCBC 402   545 if(h_.promise().has_ep_) 410   562 if(h_.promise().has_ep_)
HITCBC 403   105 std::rethrow_exception(h_.promise().ep_); 411   105 std::rethrow_exception(h_.promise().ep_);
HITCBC 404   440 return std::move(*h_.promise().result_); 412   457 return std::move(*h_.promise().result_);
405   } 413   }
406   414  
HITCBC 407   205 auto await_resume() 415   242 auto await_resume()
408   requires (! detail::is_io_result_v<T>) 416   requires (! detail::is_io_result_v<T>)
409   { 417   {
HITGNC   418 + 242 lent_ = false;
HITCBC 410   205 if(h_.promise().has_ep_) 419   242 if(h_.promise().has_ep_)
HITCBC 411   18 std::rethrow_exception(h_.promise().ep_); 420   18 std::rethrow_exception(h_.promise().ep_);
412   if constexpr (! std::is_void_v<T>) 421   if constexpr (! std::is_void_v<T>)
HITCBC 413   147 return std::move(*h_.promise().result_); 422   151 return std::move(*h_.promise().result_);
414   else 423   else
HITCBC 415   40 return; 424   73 return;
416   } 425   }
417   426  
418   /** Start the task with the awaiting coroutine's context. 427   /** Start the task with the awaiting coroutine's context.
419   428  
420   Stores `cont` as the continuation to resume on completion. 429   Stores `cont` as the continuation to resume on completion.
421   Stores `env` as the execution environment propagated to nested 430   Stores `env` as the execution environment propagated to nested
422   `co_await` expressions. Then transfers control into the task's 431   `co_await` expressions. Then transfers control into the task's
423   coroutine body via the returned handle. 432   coroutine body via the returned handle.
424   433  
425   @param cont The awaiting coroutine to resume when the task 434   @param cont The awaiting coroutine to resume when the task
426   completes. 435   completes.
427   436  
428   @param env The execution environment (executor, stop token, and 437   @param env The execution environment (executor, stop token, and
429   frame allocator). It must outlive the task. 438   frame allocator). It must outlive the task.
430   439  
431   @return The task's coroutine handle, for symmetric transfer. 440   @return The task's coroutine handle, for symmetric transfer.
432   */ 441   */
HITCBC 433   670 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 442   2001 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
434   { 443   {
HITCBC 435   670 h_.promise().set_continuation(cont); 444   2001 h_.promise().set_continuation(cont);
HITCBC 436   670 h_.promise().set_environment(env); 445   2001 h_.promise().set_environment(env);
HITGNC   446 + 2001 lent_ = true;
HITCBC 437   670 return h_; 447   2001 return h_;
438   } 448   }
439   449  
440   /** Return the coroutine handle. 450   /** Return the coroutine handle.
441   451  
442   @note Do not call `destroy()` on the returned handle while the 452   @note Do not call `destroy()` on the returned handle while the
443 - task is being awaited. The task's lifetime is normally managed 453 + task is being awaited: the awaiting frame still holds it and
444 - by `run_async`, `run`, or the awaiting parent. Manually 454 + destroys it again, which has undefined behavior. A holder that
445 - destroying a suspended task that another coroutine is awaiting 455 + gives the coroutine up passes its continuation to @ref abandon
446 - produces undefined behavior. For cooperative cancellation, use 456 + instead. (Destroying or
  457 + move-assigning over the task object itself while its frame is
  458 + lent is a different operation, and calls `std::terminate`.)
  459 + The task's lifetime is normally managed by `run_async`, `run`,
  460 + or the awaiting parent. For cooperative cancellation, use
447   `std::stop_token`. 461   `std::stop_token`.
448   462  
  463 + @note The operation, queue or primitive holding a suspended
  464 + coroutine gives it up with @ref abandon, through the
  465 + continuation it keeps, never by destroying a handle.
  466 + `handle()` belongs to the owning task object.
  467 +
449   @return The coroutine handle. 468   @return The coroutine handle.
450   */ 469   */
HITCBC 451   2073 std::coroutine_handle<promise_type> handle() const noexcept 470   110737 std::coroutine_handle<promise_type> handle() const noexcept
452   { 471   {
HITCBC 453   2073 return h_; 472   110737 return h_;
454   } 473   }
455   474  
456   /** Release ownership of the coroutine frame. 475   /** Release ownership of the coroutine frame.
457   476  
458   After calling this, destroying the task does not destroy the 477   After calling this, destroying the task does not destroy the
459   coroutine frame. The caller becomes responsible for the frame's 478   coroutine frame. The caller becomes responsible for the frame's
460   lifetime. 479   lifetime.
461   480  
462   @note The caller may call `destroy()` on the released handle 481   @note The caller may call `destroy()` on the released handle
463   only when the task has not started or has fully completed. 482   only when the task has not started or has fully completed.
464 - Destroying a suspended task that is being awaited produces 483 + Destroying the released handle of a task that is being
465 - undefined behavior. 484 + awaited is not supported and has undefined behavior.
466   485  
467   @par Postconditions 486   @par Postconditions
468   `handle()` returns a null handle. Callers needing the 487   `handle()` returns a null handle. Callers needing the
469   original handle must save it, via @ref handle, before 488   original handle must save it, via @ref handle, before
470   calling this. 489   calling this.
471   */ 490   */
HITCBC 472   1990 void release() noexcept 491   110594 void release() noexcept
473   { 492   {
HITCBC 474   1990 h_ = nullptr; 493   110594 h_ = nullptr;
HITCBC 475   1990 } 494   110594 }
476   495  
477   /** Copy construction is disabled; a task uniquely owns its frame. 496   /** Copy construction is disabled; a task uniquely owns its frame.
478   497  
479   @param other The task that would be copied. 498   @param other The task that would be copied.
480   */ 499   */
481   task(task const& other) = delete; 500   task(task const& other) = delete;
482   501  
483   /** Copy assignment is disabled; a task uniquely owns its frame. 502   /** Copy assignment is disabled; a task uniquely owns its frame.
484   503  
485   @param other The task that would be assigned from. 504   @param other The task that would be assigned from.
486   505  
487   @return A reference to `*this`. 506   @return A reference to `*this`.
488   */ 507   */
489   task& operator=(task const& other) = delete; 508   task& operator=(task const& other) = delete;
490   509  
491   /** Construct by moving, transferring ownership of the frame. 510   /** Construct by moving, transferring ownership of the frame.
492   511  
493   @par Postconditions 512   @par Postconditions
494   `other` is empty and must not be awaited. 513   `other` is empty and must not be awaited.
495   514  
496   @param other The task to move from. 515   @param other The task to move from.
497   */ 516   */
HITCBC 498   3068 task(task&& other) noexcept 517   4070 task(task&& other) noexcept
HITCBC 499   3068 : h_(std::exchange(other.h_, nullptr)) 518   4070 : h_(std::exchange(other.h_, nullptr))
500   { 519   {
HITCBC 501   3068 } 520   4070 }
502   521  
503   /** Assign by moving, transferring ownership of the frame. 522   /** Assign by moving, transferring ownership of the frame.
504   523  
505   If this task already owns a coroutine frame, that frame is 524   If this task already owns a coroutine frame, that frame is
506   destroyed first. Self-assignment is a no-op. 525   destroyed first. Self-assignment is a no-op.
507   526  
  527 + @par Preconditions
  528 + The frame being replaced is not lent: no parent is suspended
  529 + awaiting it. Replacing a lent frame calls `std::terminate`.
  530 +
508   @par Postconditions 531   @par Postconditions
509   `other` is empty and must not be awaited. 532   `other` is empty and must not be awaited.
510   533  
511   @param other The task to move from. 534   @param other The task to move from.
512   535  
513   @return A reference to `*this`. 536   @return A reference to `*this`.
514   */ 537   */
HITGIC 515   task& operator=(task&& other) noexcept 538   1 task& operator=(task&& other) noexcept
516   { 539   {
HITGIC 517   if(this != &other) 540   1 if(this != &other)
518   { 541   {
HITGIC 519 - if(h_) 542 + 1 destroy_frame();
520 - h_.destroy();  
HITGIC 521   h_ = std::exchange(other.h_, nullptr); 543   1 h_ = std::exchange(other.h_, nullptr);
522   } 544   }
HITGIC 523   return *this; 545   1 return *this;
524   } 546   }
525   547  
526   private: 548   private:
  549 + // Set while a parent is suspended awaiting this task.
  550 + bool lent_ = false;
  551 +
HITCBC 527   2744 explicit task(std::coroutine_handle<promise_type> h) 552   112709 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 528   2744 : h_(h) 553   112709 : h_(h)
529   { 554   {
HITGNC   555 + 112709 }
  556 +
HITGNC   557 + 116780 void destroy_frame() noexcept
  558 + {
HITGNC   559 + 116780 if(!h_)
HITGNC   560 + 114665 return;
HITGNC   561 + 2115 if(lent_ && !h_.done())
  562 + {
  563 + // Only the unwind of an abandoned chain destroys a frame
  564 + // that is still awaited.
HITGNC   565 + 1278 auto* owner = h_.promise().environment()->owner;
HITGNC   566 + 1278 if(!owner || !owner->abandoned())
  567 + std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
  568 + }
HITGNC   569 + 2115 std::exchange(h_, nullptr).destroy();
HITGNC   570 + 2115 lent_ = false;
ECB 530   2744 } 571   }
531   }; 572   };
532   573  
533   } // namespace capy 574   } // namespace capy
534   } // namespace boost 575   } // namespace boost
535   576  
536   #endif 577   #endif