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/context_awaitable.hpp> 16   #include <boost/capy/concept/context_awaitable.hpp>
17   #include <boost/capy/ex/context_awaitable_promise_base.hpp> 17   #include <boost/capy/ex/context_awaitable_promise_base.hpp>
18   #include <boost/capy/ex/await_context.hpp> 18   #include <boost/capy/ex/await_context.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/results.hpp> 21   #include <boost/capy/results.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   899 void return_value(T value) 40   899 void return_value(T value)
41   { 41   {
HITCBC 42   899 result_ = std::move(value); 42   899 result_ = std::move(value);
HITCBC 43   899 } 43   899 }
44   44  
HITCBC 45   284 T&& result() noexcept 45   284 T&& result() noexcept
46   { 46   {
HITCBC 47   284 return std::move(*result_); 47   284 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   4143 void return_void() 54   4149 void return_void()
55   { 55   {
HITCBC 56   4143 } 56   4149 }
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. 88   awaits, or by reading the token itself.
89   89  
90   @par Await-returns 90   @par Await-returns
91   The value the body passed to `co_return`, moved out of the task, or 91   The value the body passed to `co_return`, moved out of the task, or
92   nothing when `T` is `void`. 92   nothing when `T` is `void`.
93   93  
94   If the body exits via an unhandled exception, that exception is 94   If the body exits via an unhandled exception, that exception is
95   rethrown instead. 95   rethrown instead.
96   96  
97   @par Await-postcondition 97   @par Await-postcondition
98   The task's coroutine has run to completion and is suspended at its 98   The task's coroutine has run to completion and is suspended at its
99   final suspend point. The task still owns the frame, but not the 99   final suspend point. The task still owns the frame, but not the
100   result: the await moves it out, so a task must not be awaited twice. 100   result: the await moves it out, so a task must not be awaited twice.
101   101  
102   @par Thread Safety 102   @par Thread Safety
103   Distinct objects: Safe. 103   Distinct objects: Safe.
104   Shared objects: Unsafe. 104   Shared objects: Unsafe.
105   105  
106   @par Example 106   @par Example
107   107  
108   @par !example example 108   @par !example example
109   109  
110   110  
111   @tparam T The result type. Use `task<>` for `task<void>`. 111   @tparam T The result type. Use `task<>` for `task<void>`.
112   112  
113   @see @ref ContextRunnable, @ref ContextAwaitable, @ref run, @ref run_async 113   @see @ref ContextRunnable, @ref ContextAwaitable, @ref run, @ref run_async
114   */ 114   */
115   template<typename T = void> 115   template<typename T = void>
116   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 116   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
117   task 117   task
118   { 118   {
119   /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `context_awaitable_promise_base`. 119   /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `context_awaitable_promise_base`.
120   120  
121   This is the promise object the compiler associates with a 121   This is the promise object the compiler associates with a
122   `task<T>` coroutine. It satisfies the coroutine promise 122   `task<T>` coroutine. It satisfies the coroutine promise
123   requirements and participates in the I/O awaitable protocol via 123   requirements and participates in the I/O awaitable protocol via
124   @ref context_awaitable_promise_base. It is part of the coroutine 124   @ref context_awaitable_promise_base. It is part of the coroutine
125   machinery and is not intended to be used directly by callers. 125   machinery and is not intended to be used directly by callers.
126   126  
127   Result storage and `return_value`/`return_void` are provided by 127   Result storage and `return_value`/`return_void` are provided by
128   `detail::task_return_base<T>`. 128   `detail::task_return_base<T>`.
129   129  
130   @see @ref context_awaitable_promise_base, @ref ContextRunnable 130   @see @ref context_awaitable_promise_base, @ref ContextRunnable
131   */ 131   */
132   struct promise_type 132   struct promise_type
133   : context_awaitable_promise_base<promise_type> 133   : context_awaitable_promise_base<promise_type>
134   , detail::task_return_base<T> 134   , detail::task_return_base<T>
135   { 135   {
136   private: 136   private:
137   friend task; 137   friend task;
138   union { std::exception_ptr ep_; }; 138   union { std::exception_ptr ep_; };
139   bool has_ep_; 139   bool has_ep_;
140   140  
141   public: 141   public:
142   /// Construct the promise with no stored exception. 142   /// Construct the promise with no stored exception.
HITCBC 143   109243 promise_type() noexcept 143   109239 promise_type() noexcept
HITCBC 144   109243 : has_ep_(false) 144   109239 : has_ep_(false)
145   { 145   {
HITCBC 146   109243 } 146   109239 }
147   147  
148   /// Destroy the promise, releasing any stored exception. 148   /// Destroy the promise, releasing any stored exception.
HITCBC 149   109243 ~promise_type() 149   109239 ~promise_type()
150   { 150   {
HITCBC 151   109243 if(has_ep_) 151   109239 if(has_ep_)
HITCBC 152   485 ep_.~exception_ptr(); 152   485 ep_.~exception_ptr();
HITCBC 153   109243 } 153   109239 }
154   154  
155   /** Return the exception captured by the coroutine body, if any. 155   /** Return the exception captured by the coroutine body, if any.
156   156  
157   @return The stored exception, or a null `std::exception_ptr` 157   @return The stored exception, or a null `std::exception_ptr`
158   if the coroutine did not exit via an unhandled exception. 158   if the coroutine did not exit via an unhandled exception.
159   */ 159   */
HITCBC 160   5018 std::exception_ptr exception() const noexcept 160   5024 std::exception_ptr exception() const noexcept
161   { 161   {
HITCBC 162   5018 if(has_ep_) 162   5024 if(has_ep_)
HITCBC 163   726 return ep_; 163   726 return ep_;
HITCBC 164   4292 return {}; 164   4298 return {};
165   } 165   }
166   166  
167   /** Return the owning `task` for this coroutine. 167   /** Return the owning `task` for this coroutine.
168   168  
169   Called by the compiler to produce the object returned to the 169   Called by the compiler to produce the object returned to the
170   caller when the coroutine is created. 170   caller when the coroutine is created.
171   171  
172   @return A `task` owning the coroutine frame. 172   @return A `task` owning the coroutine frame.
173   */ 173   */
HITCBC 174   109243 task get_return_object() 174   109239 task get_return_object()
175   { 175   {
HITCBC 176   109243 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 176   109239 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
177   } 177   }
178   178  
179   /** Return the initial-suspend awaiter. 179   /** Return the initial-suspend awaiter.
180   180  
181   The coroutine always suspends at the initial suspend point, 181   The coroutine always suspends at the initial suspend point,
182   so the body does not start until the task is awaited. When the 182   so the body does not start until the task is awaited. When the
183   body is resumed, the awaiter restores the thread-local frame 183   body is resumed, the awaiter restores the thread-local frame
184   allocator from the stored environment. 184   allocator from the stored environment.
185   185  
186   @return An awaiter that suspends unconditionally. 186   @return An awaiter that suspends unconditionally.
187   */ 187   */
HITCBC 188   109243 auto initial_suspend() noexcept 188   109239 auto initial_suspend() noexcept
189   { 189   {
190   struct awaiter 190   struct awaiter
191   { 191   {
192   promise_type* p_; 192   promise_type* p_;
193   193  
HITCBC 194   109243 bool await_ready() const noexcept 194   109239 bool await_ready() const noexcept
195   { 195   {
HITCBC 196   109243 return false; 196   109239 return false;
197   } 197   }
198   198  
HITCBC 199   109243 void await_suspend(std::coroutine_handle<>) const noexcept 199   109239 void await_suspend(std::coroutine_handle<>) const noexcept
200   { 200   {
HITCBC 201   109243 } 201   109239 }
202   202  
HITCBC 203   108160 void await_resume() const noexcept 203   108156 void await_resume() const noexcept
204   { 204   {
205   // Restore TLS when body starts executing 205   // Restore TLS when body starts executing
HITCBC 206   108160 set_current_frame_allocator(p_->environment()->frame_allocator); 206   108156 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 207   108160 } 207   108156 }
208   }; 208   };
HITCBC 209   109243 return awaiter{this}; 209   109239 return awaiter{this};
210   } 210   }
211   211  
212   /** Return the final-suspend awaiter. 212   /** Return the final-suspend awaiter.
213   213  
214   The coroutine always suspends at the final suspend point. The 214   The coroutine always suspends at the final suspend point. The
215   awaiter's `await_suspend` performs symmetric transfer to the 215   awaiter's `await_suspend` performs symmetric transfer to the
216   stored continuation (consuming it), resuming the awaiting 216   stored continuation (consuming it), resuming the awaiting
217   coroutine. 217   coroutine.
218   218  
219   @return An awaiter that suspends and transfers to the 219   @return An awaiter that suspends and transfers to the
220   continuation. 220   continuation.
221   */ 221   */
HITCBC 222   5527 auto final_suspend() noexcept 222   5533 auto final_suspend() noexcept
223   { 223   {
224   struct awaiter 224   struct awaiter
225   { 225   {
226   promise_type* p_; 226   promise_type* p_;
227   227  
HITCBC 228   5527 bool await_ready() const noexcept 228   5533 bool await_ready() const noexcept
229   { 229   {
HITCBC 230   5527 return false; 230   5533 return false;
231   } 231   }
232   232  
HITCBC 233   5527 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 233   5533 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
234   { 234   {
HITCBC 235   5527 return p_->continuation(); 235   5533 return p_->continuation();
236   } 236   }
237   237  
238   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 238   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
239   }; 239   };
HITCBC 240   5527 return awaiter{this}; 240   5533 return awaiter{this};
241   } 241   }
242   242  
243   /** Capture the in-flight exception from the coroutine body. 243   /** Capture the in-flight exception from the coroutine body.
244   244  
245   Called by the compiler when the coroutine body exits via an 245   Called by the compiler when the coroutine body exits via an
246   unhandled exception. The captured exception is rethrown when 246   unhandled exception. The captured exception is rethrown when
247   the task is awaited. 247   the task is awaited.
248   */ 248   */
HITCBC 249   485 void unhandled_exception() noexcept 249   485 void unhandled_exception() noexcept
250   { 250   {
HITCBC 251   485 new (&ep_) std::exception_ptr(std::current_exception()); 251   485 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 252   485 has_ep_ = true; 252   485 has_ep_ = true;
HITCBC 253   485 } 253   485 }
254   254  
255   /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable. 255   /** Awaiter wrapping a nested `co_await` of an @ref ContextAwaitable.
256   256  
257   Forwards the environment to the inner awaitable's 257   Forwards the environment to the inner awaitable's
258   environment-taking `await_suspend` and restores the 258   environment-taking `await_suspend` and restores the
259   thread-local frame allocator before the body resumes. 259   thread-local frame allocator before the body resumes.
260   260  
261   @tparam Awaitable The awaitable being transformed. 261   @tparam Awaitable The awaitable being transformed.
262   */ 262   */
263   template<class Awaitable> 263   template<class Awaitable>
264   struct transform_awaiter 264   struct transform_awaiter
265   { 265   {
266   /// The wrapped awaitable, decayed and stored by value. 266   /// The wrapped awaitable, decayed and stored by value.
267   std::decay_t<Awaitable> a_; 267   std::decay_t<Awaitable> a_;
268   268  
269   /// The promise of the coroutine performing the `co_await`. 269   /// The promise of the coroutine performing the `co_await`.
270   promise_type* p_; 270   promise_type* p_;
271   271  
272   /** Report whether the wrapped awaitable is already complete. 272   /** Report whether the wrapped awaitable is already complete.
273   273  
274   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, 274   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
275   this always returns `false`, and @ref await_suspend asks 275   this always returns `false`, and @ref await_suspend asks
276   the wrapped awaitable instead. See 276   the wrapped awaitable instead. See
277   `detail::await_ready_then_suspend`. 277   `detail::await_ready_then_suspend`.
278   278  
279   @return The wrapped awaitable's own `await_ready` result: 279   @return The wrapped awaitable's own `await_ready` result:
280   `true` if no suspension is needed. Always `false` where 280   `true` if no suspension is needed. Always `false` where
281   the workaround is on. 281   the workaround is on.
282   */ 282   */
HITCBC 283   108226 bool await_ready() noexcept 283   108222 bool await_ready() noexcept
284   { 284   {
285   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND 285   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
286   return false; 286   return false;
287   #else 287   #else
HITCBC 288   108226 return a_.await_ready(); 288   108222 return a_.await_ready();
289   #endif 289   #endif
290   } 290   }
291   291  
292   /** Restore the frame allocator, then resume the wrapped 292   /** Restore the frame allocator, then resume the wrapped
293   awaitable. 293   awaitable.
294   294  
295   Reinstalls the thread-local frame allocator from the stored 295   Reinstalls the thread-local frame allocator from the stored
296   environment before the body continues. This is needed 296   environment before the body continues. This is needed
297   because the resumption may arrive on a different thread 297   because the resumption may arrive on a different thread
298   than the one that suspended. 298   than the one that suspended.
299   299  
300   @return The wrapped awaitable's await-result, forwarded 300   @return The wrapped awaitable's await-result, forwarded
301   unchanged. 301   unchanged.
302   */ 302   */
HITCBC 303   5593 decltype(auto) await_resume() 303   5599 decltype(auto) await_resume()
304   { 304   {
305   // Restore TLS before body resumes 305   // Restore TLS before body resumes
HITCBC 306   5593 set_current_frame_allocator(p_->environment()->frame_allocator); 306   5599 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 307   5593 return a_.await_resume(); 307   5599 return a_.await_resume();
308   } 308   }
309   309  
310   /** Suspend by calling the wrapped awaitable with the 310   /** Suspend by calling the wrapped awaitable with the
311   environment. 311   environment.
312   312  
313   This is the plain `await_suspend` the compiler calls for the 313   This is the plain `await_suspend` the compiler calls for the
314   nested `co_await`. It forwards to the wrapped awaitable's 314   nested `co_await`. It forwards to the wrapped awaitable's
315   @ref ContextAwaitable overload, supplying the promise's stored 315   @ref ContextAwaitable overload, supplying the promise's stored
316   environment as the second argument. It then hands back 316   environment as the second argument. It then hands back
317   that call's result unchanged, so the wrapped awaitable's 317   that call's result unchanged, so the wrapped awaitable's
318   suspension decision, whatever form it takes, is preserved. 318   suspension decision, whatever form it takes, is preserved.
319   319  
320   @param h The coroutine performing the `co_await`. 320   @param h The coroutine performing the `co_await`.
321   321  
322   @return Whatever the wrapped awaitable's `await_suspend` 322   @return Whatever the wrapped awaitable's `await_suspend`
323   returns. When that is a `std::coroutine_handle<>`, the 323   returns. When that is a `std::coroutine_handle<>`, the
324   handle is routed through `detail::symmetric_transfer`. 324   handle is routed through `detail::symmetric_transfer`.
325   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on, 325   Where `BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND` is on,
326   this function instead returns `bool` from 326   this function instead returns `bool` from
327   `detail::await_ready_then_suspend`. It first asks the 327   `detail::await_ready_then_suspend`. It first asks the
328   wrapped awaitable's `await_ready`. When that reports 328   wrapped awaitable's `await_ready`. When that reports
329   ready, it returns `false` to continue without 329   ready, it returns `false` to continue without
330   suspending. Otherwise it resumes any returned handle on 330   suspending. Otherwise it resumes any returned handle on
331   the current stack and returns `true`. Where the 331   the current stack and returns `true`. Where the
332   workaround is off, a returned handle is passed back 332   workaround is off, a returned handle is passed back
333   unchanged for symmetric transfer. 333   unchanged for symmetric transfer.
334   */ 334   */
335   template<class Promise> 335   template<class Promise>
HITCBC 336   107601 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 336   107593 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
337   { 337   {
338   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND 338   #if BOOST_CAPY_SYMMETRIC_TRANSFER_WORKAROUND
339   return detail::await_ready_then_suspend(a_, h, p_->environment()); 339   return detail::await_ready_then_suspend(a_, h, p_->environment());
340   #else 340   #else
341   using R = decltype(a_.await_suspend(h, p_->environment())); 341   using R = decltype(a_.await_suspend(h, p_->environment()));
342   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 342   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 343   106563 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 343   106555 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
344   else 344   else
HITCBC 345   1038 return a_.await_suspend(h, p_->environment()); 345   1038 return a_.await_suspend(h, p_->environment());
346   #endif 346   #endif
347   } 347   }
348   }; 348   };
349   349  
350   /** Transform a nested awaitable before `co_await`. 350   /** Transform a nested awaitable before `co_await`.
351   351  
352   Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the 352   Wraps an @ref ContextAwaitable in a @ref transform_awaiter so the
353   coroutine's environment is propagated into it. A diagnostic 353   coroutine's environment is propagated into it. A diagnostic
354   is emitted if the awaitable does not satisfy @ref ContextAwaitable. 354   is emitted if the awaitable does not satisfy @ref ContextAwaitable.
355   355  
356   @param a The awaitable expression from `co_await a`. 356   @param a The awaitable expression from `co_await a`.
357   357  
358   @return A @ref transform_awaiter wrapping `a`. 358   @return A @ref transform_awaiter wrapping `a`.
359   */ 359   */
360   template<class Awaitable> 360   template<class Awaitable>
HITCBC 361   108226 auto transform_awaitable(Awaitable&& a) 361   108222 auto transform_awaitable(Awaitable&& a)
362   { 362   {
363   using A = std::decay_t<Awaitable>; 363   using A = std::decay_t<Awaitable>;
364   if constexpr (ContextAwaitable<A>) 364   if constexpr (ContextAwaitable<A>)
365   { 365   {
366   return transform_awaiter<Awaitable>{ 366   return transform_awaiter<Awaitable>{
HITCBC 367   213935 std::forward<Awaitable>(a), this}; 367   213927 std::forward<Awaitable>(a), this};
368   } 368   }
369   else 369   else
370   { 370   {
371   static_assert(ContextAwaitable<A>, "requires ContextAwaitable"); 371   static_assert(ContextAwaitable<A>, "requires ContextAwaitable");
372   } 372   }
HITCBC 373   105709 } 373   105705 }
374   }; 374   };
375   375  
376   /** Handle to the owned coroutine frame. 376   /** Handle to the owned coroutine frame.
377   377  
378   Null when the task is empty (for example after a move or after 378   Null when the task is empty (for example after a move or after
379   @ref release). Prefer @ref handle to read this; the member is 379   @ref release). Prefer @ref handle to read this; the member is
380   public for use by the coroutine machinery. 380   public for use by the coroutine machinery.
381   */ 381   */
382   std::coroutine_handle<promise_type> h_; 382   std::coroutine_handle<promise_type> h_;
383   383  
384   /** Destroy the task and its coroutine frame if owned. 384   /** Destroy the task and its coroutine frame if owned.
385   385  
386   @par Preconditions 386   @par Preconditions
387   The frame is not lent: no parent is suspended awaiting it, 387   The frame is not lent: no parent is suspended awaiting it,
388   unless its chain is being unwound after an @ref abandon. 388   unless its chain is being unwound after an @ref abandon.
389   Destroying a lent frame otherwise calls `std::terminate`; end 389   Destroying a lent frame otherwise calls `std::terminate`; end
390   a running chain by requesting stop, or by destroying its 390   a running chain by requesting stop, or by destroying its
391   execution context. 391   execution context.
392   */ 392   */
HITCBC 393   113317 ~task() 393   113313 ~task()
394   { 394   {
HITCBC 395   113317 destroy_frame(); 395   113313 destroy_frame();
HITCBC 396   113317 } 396   113313 }
397   397  
398   /** Report whether the awaited task is already complete. 398   /** Report whether the awaited task is already complete.
399   399  
400   Always returns `false`; a task is lazy and has not started when 400   Always returns `false`; a task is lazy and has not started when
401   it is awaited, so the awaiting coroutine always suspends. 401   it is awaited, so the awaiting coroutine always suspends.
402   402  
403   @return `false`. 403   @return `false`.
404   */ 404   */
HITCBC 405   2108 bool await_ready() const noexcept 405   2108 bool await_ready() const noexcept
406   { 406   {
HITCBC 407   2108 return false; 407   2108 return false;
408   } 408   }
409   409  
410   /** Return the task's result, rethrowing any captured exception. 410   /** Return the task's result, rethrowing any captured exception.
411   411  
412   If the coroutine body exited via an unhandled exception, that 412   If the coroutine body exited via an unhandled exception, that
413   exception is rethrown here. Otherwise the result is returned by 413   exception is rethrown here. Otherwise the result is returned by
414   move (for `task<T>`) or nothing is returned (for `task<void>`). 414   move (for `task<T>`) or nothing is returned (for `task<void>`).
415   415  
416   @return The result value for non-void `T`; otherwise `void`. 416   @return The result value for non-void `T`; otherwise `void`.
417   417  
418   @throws The exception captured by the coroutine body, if any. 418   @throws The exception captured by the coroutine body, if any.
419   419  
420   @note Discarding a `results` silently drops the error 420   @note Discarding a `results` silently drops the error
421   code, so that overload is marked `[[nodiscard]]`. 421   code, so that overload is marked `[[nodiscard]]`.
422   */ 422   */
HITCBC 423   566 [[nodiscard]] auto await_resume() 423   566 [[nodiscard]] auto await_resume()
424   requires detail::is_results_v<T> 424   requires detail::is_results_v<T>
425   { 425   {
HITCBC 426   566 lent_ = false; 426   566 lent_ = false;
HITCBC 427   566 if(h_.promise().has_ep_) 427   566 if(h_.promise().has_ep_)
HITCBC 428   104 std::rethrow_exception(h_.promise().ep_); 428   104 std::rethrow_exception(h_.promise().ep_);
HITCBC 429   462 return std::move(*h_.promise().result_); 429   462 return std::move(*h_.promise().result_);
430   } 430   }
431   431  
HITCBC 432   243 auto await_resume() 432   243 auto await_resume()
433   requires (! detail::is_results_v<T>) 433   requires (! detail::is_results_v<T>)
434   { 434   {
HITCBC 435   243 lent_ = false; 435   243 lent_ = false;
HITCBC 436   243 if(h_.promise().has_ep_) 436   243 if(h_.promise().has_ep_)
HITCBC 437   17 std::rethrow_exception(h_.promise().ep_); 437   17 std::rethrow_exception(h_.promise().ep_);
438   if constexpr (! std::is_void_v<T>) 438   if constexpr (! std::is_void_v<T>)
HITCBC 439   151 return std::move(*h_.promise().result_); 439   151 return std::move(*h_.promise().result_);
440   else 440   else
HITCBC 441   75 return; 441   75 return;
442   } 442   }
443   443  
444   /** Start the task with the awaiting coroutine's context. 444   /** Start the task with the awaiting coroutine's context.
445   445  
446   Stores `cont` as the continuation to resume on completion. 446   Stores `cont` as the continuation to resume on completion.
447   Stores `env` as the execution environment propagated to nested 447   Stores `env` as the execution environment propagated to nested
448   `co_await` expressions. Then transfers control into the task's 448   `co_await` expressions. Then transfers control into the task's
449   coroutine body via the returned handle. 449   coroutine body via the returned handle.
450   450  
451   @param cont The awaiting coroutine to resume when the task 451   @param cont The awaiting coroutine to resume when the task
452   completes. 452   completes.
453   453  
454   @param env The execution environment (executor, stop token, and 454   @param env The execution environment (executor, stop token, and
455   frame allocator). It must outlive the task. 455   frame allocator). It must outlive the task.
456   456  
457   @return The task's coroutine handle, for symmetric transfer. 457   @return The task's coroutine handle, for symmetric transfer.
458   */ 458   */
HITCBC 459   2005 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, await_context const* env) 459   2005 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, await_context const* env)
460   { 460   {
HITCBC 461   2005 h_.promise().set_continuation(cont); 461   2005 h_.promise().set_continuation(cont);
HITCBC 462   2005 h_.promise().set_environment(env); 462   2005 h_.promise().set_environment(env);
HITCBC 463   2005 lent_ = true; 463   2005 lent_ = true;
HITCBC 464   2005 return h_; 464   2005 return h_;
465   } 465   }
466   466  
467   /** Return the coroutine handle. 467   /** Return the coroutine handle.
468   468  
469   @note Do not call `destroy()` on the returned handle while the 469   @note Do not call `destroy()` on the returned handle while the
470   task is being awaited: the awaiting frame still holds it and 470   task is being awaited: the awaiting frame still holds it and
471   destroys it again, which has undefined behavior. A holder that 471   destroys it again, which has undefined behavior. A holder that
472   gives the coroutine up passes its continuation to @ref abandon 472   gives the coroutine up passes its continuation to @ref abandon
473   instead. (Destroying or 473   instead. (Destroying or
474   move-assigning over the task object itself while its frame is 474   move-assigning over the task object itself while its frame is
475   lent is a different operation, and calls `std::terminate`.) 475   lent is a different operation, and calls `std::terminate`.)
476   The task's lifetime is normally managed by `run_async`, `run`, 476   The task's lifetime is normally managed by `run_async`, `run`,
477   or the awaiting parent. For cooperative cancellation, use 477   or the awaiting parent. For cooperative cancellation, use
478   `std::stop_token`. 478   `std::stop_token`.
479   479  
480   @note The operation, queue or primitive holding a suspended 480   @note The operation, queue or primitive holding a suspended
481   coroutine gives it up with @ref abandon, through the 481   coroutine gives it up with @ref abandon, through the
482   continuation it keeps, never by destroying a handle. 482   continuation it keeps, never by destroying a handle.
483   `handle()` belongs to the owning task object. 483   `handle()` belongs to the owning task object.
484   484  
485   @return The coroutine handle. 485   @return The coroutine handle.
486   */ 486   */
HITCBC 487   107267 std::coroutine_handle<promise_type> handle() const noexcept 487   107263 std::coroutine_handle<promise_type> handle() const noexcept
488   { 488   {
HITCBC 489   107267 return h_; 489   107263 return h_;
490   } 490   }
491   491  
492   /** Release ownership of the coroutine frame. 492   /** Release ownership of the coroutine frame.
493   493  
494   After calling this, destroying the task does not destroy the 494   After calling this, destroying the task does not destroy the
495   coroutine frame. The caller becomes responsible for the frame's 495   coroutine frame. The caller becomes responsible for the frame's
496   lifetime. 496   lifetime.
497   497  
498   @note The caller may call `destroy()` on the released handle 498   @note The caller may call `destroy()` on the released handle
499   only when the task has not started or has fully completed. 499   only when the task has not started or has fully completed.
500   Destroying the released handle of a task that is being 500   Destroying the released handle of a task that is being
501   awaited is not supported and has undefined behavior. 501   awaited is not supported and has undefined behavior.
502   502  
503   @par Postconditions 503   @par Postconditions
504   `handle()` returns a null handle. Callers needing the 504   `handle()` returns a null handle. Callers needing the
505   original handle must save it, via @ref handle, before 505   original handle must save it, via @ref handle, before
506   calling this. 506   calling this.
507   */ 507   */
HITCBC 508   107124 void release() noexcept 508   107120 void release() noexcept
509   { 509   {
HITCBC 510   107124 h_ = nullptr; 510   107120 h_ = nullptr;
HITCBC 511   107124 } 511   107120 }
512   512  
513   /** Copy construction is disabled; a task uniquely owns its frame. 513   /** Copy construction is disabled; a task uniquely owns its frame.
514   514  
515   @param other The task that would be copied. 515   @param other The task that would be copied.
516   */ 516   */
517   task(task const& other) = delete; 517   task(task const& other) = delete;
518   518  
519   /** Copy assignment is disabled; a task uniquely owns its frame. 519   /** Copy assignment is disabled; a task uniquely owns its frame.
520   520  
521   @param other The task that would be assigned from. 521   @param other The task that would be assigned from.
522   522  
523   @return A reference to `*this`. 523   @return A reference to `*this`.
524   */ 524   */
525   task& operator=(task const& other) = delete; 525   task& operator=(task const& other) = delete;
526   526  
527   /** Construct by moving, transferring ownership of the frame. 527   /** Construct by moving, transferring ownership of the frame.
528   528  
529   @par Postconditions 529   @par Postconditions
530   `other` is empty and must not be awaited. 530   `other` is empty and must not be awaited.
531   531  
532   @param other The task to move from. 532   @param other The task to move from.
533   */ 533   */
HITCBC 534   4074 task(task&& other) noexcept 534   4074 task(task&& other) noexcept
HITCBC 535   4074 : h_(std::exchange(other.h_, nullptr)) 535   4074 : h_(std::exchange(other.h_, nullptr))
536   { 536   {
HITCBC 537   4074 } 537   4074 }
538   538  
539   /** Assign by moving, transferring ownership of the frame. 539   /** Assign by moving, transferring ownership of the frame.
540   540  
541   If this task already owns a coroutine frame, that frame is 541   If this task already owns a coroutine frame, that frame is
542   destroyed first. Self-assignment is a no-op. 542   destroyed first. Self-assignment is a no-op.
543   543  
544   @par Preconditions 544   @par Preconditions
545   The frame being replaced is not lent: no parent is suspended 545   The frame being replaced is not lent: no parent is suspended
546   awaiting it. Replacing a lent frame calls `std::terminate`. 546   awaiting it. Replacing a lent frame calls `std::terminate`.
547   547  
548   @par Postconditions 548   @par Postconditions
549   `other` is empty and must not be awaited. 549   `other` is empty and must not be awaited.
550   550  
551   @param other The task to move from. 551   @param other The task to move from.
552   552  
553   @return A reference to `*this`. 553   @return A reference to `*this`.
554   */ 554   */
HITCBC 555   1 task& operator=(task&& other) noexcept 555   1 task& operator=(task&& other) noexcept
556   { 556   {
HITCBC 557   1 if(this != &other) 557   1 if(this != &other)
558   { 558   {
HITCBC 559   1 destroy_frame(); 559   1 destroy_frame();
HITCBC 560   1 h_ = std::exchange(other.h_, nullptr); 560   1 h_ = std::exchange(other.h_, nullptr);
561   } 561   }
HITCBC 562   1 return *this; 562   1 return *this;
563   } 563   }
564   564  
565   private: 565   private:
566   // Set while a parent is suspended awaiting this task. 566   // Set while a parent is suspended awaiting this task.
567   bool lent_ = false; 567   bool lent_ = false;
568   568  
HITCBC 569   109243 explicit task(std::coroutine_handle<promise_type> h) 569   109239 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 570   109243 : h_(h) 570   109239 : h_(h)
571   { 571   {
HITCBC 572   109243 } 572   109239 }
573   573  
HITCBC 574   113318 void destroy_frame() noexcept 574   113314 void destroy_frame() noexcept
575   { 575   {
HITCBC 576   113318 if(!h_) 576   113314 if(!h_)
HITCBC 577   111199 return; 577   111195 return;
HITCBC 578   2119 if(lent_ && !h_.done()) 578   2119 if(lent_ && !h_.done())
579   { 579   {
580   // Only the unwind of an abandoned chain destroys a frame 580   // Only the unwind of an abandoned chain destroys a frame
581   // that is still awaited. 581   // that is still awaited.
HITCBC 582   1277 auto* owner = h_.promise().environment()->owner; 582   1277 auto* owner = h_.promise().environment()->owner;
HITCBC 583   1277 if(!owner || !owner->abandoned()) 583   1277 if(!owner || !owner->abandoned())
584   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process 584   std::terminate(); // LCOV_EXCL_LINE reached only in a death test's child process
585   } 585   }
HITCBC 586   2119 std::exchange(h_, nullptr).destroy(); 586   2119 std::exchange(h_, nullptr).destroy();
HITCBC 587   2119 lent_ = false; 587   2119 lent_ = false;
588   } 588   }
589   }; 589   };
590   590  
591   } // namespace capy 591   } // namespace capy
592   } // namespace boost 592   } // namespace boost
593   593  
594   #endif 594   #endif