100.00% Lines (80/80) 100.00% Functions (28/28)
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   870 void return_value(T value) 40   870 void return_value(T value)
41   { 41   {
HITCBC 42   870 result_ = std::move(value); 42   870 result_ = std::move(value);
HITCBC 43   870 } 43   870 }
44   44  
HITCBC 45   273 T&& result() noexcept 45   273 T&& result() noexcept
46   { 46   {
HITCBC 47   273 return std::move(*result_); 47   273 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   1251 void return_void() 54   1264 void return_void()
55   { 55   {
HITCBC 56   1251 } 56   1264 }
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   @code 109   @code
110   task<int> compute_value() 110   task<int> compute_value()
111   { 111   {
112   auto [ec, n] = co_await stream.read_some( buf ); 112   auto [ec, n] = co_await stream.read_some( buf );
113   if( ec ) 113   if( ec )
114   co_return 0; 114   co_return 0;
115   co_return process( buf, n ); 115   co_return process( buf, n );
116   } 116   }
117   117  
118   task<> run_session( tcp_socket sock ) 118   task<> run_session( tcp_socket sock )
119   { 119   {
120   int result = co_await compute_value(); 120   int result = co_await compute_value();
121   // ... 121   // ...
122   } 122   }
123   @endcode 123   @endcode
124   124  
125   @tparam T The result type. Use `task<>` for `task<void>`. 125   @tparam T The result type. Use `task<>` for `task<void>`.
126   126  
127   @see IoRunnable, IoAwaitable, run, run_async 127   @see IoRunnable, IoAwaitable, run, run_async
128   */ 128   */
129   template<typename T = void> 129   template<typename T = void>
130   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 130   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
131   task 131   task
132   { 132   {
133   /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `io_awaitable_promise_base`. 133   /** Stores `task<T>`'s result and joins the I/O awaitable protocol via `io_awaitable_promise_base`.
134   134  
135   This is the promise object the compiler associates with a 135   This is the promise object the compiler associates with a
136   `task<T>` coroutine. It satisfies the coroutine promise 136   `task<T>` coroutine. It satisfies the coroutine promise
137   requirements and participates in the I/O awaitable protocol via 137   requirements and participates in the I/O awaitable protocol via
138   @ref io_awaitable_promise_base. It is part of the coroutine 138   @ref io_awaitable_promise_base. It is part of the coroutine
139   machinery and is not intended to be used directly by callers. 139   machinery and is not intended to be used directly by callers.
140   140  
141   Result storage and `return_value`/`return_void` are provided by 141   Result storage and `return_value`/`return_void` are provided by
142   `detail::task_return_base<T>`. 142   `detail::task_return_base<T>`.
143   143  
144   @see io_awaitable_promise_base, IoRunnable 144   @see io_awaitable_promise_base, IoRunnable
145   */ 145   */
146   struct promise_type 146   struct promise_type
147   : io_awaitable_promise_base<promise_type> 147   : io_awaitable_promise_base<promise_type>
148   , detail::task_return_base<T> 148   , detail::task_return_base<T>
149   { 149   {
150   private: 150   private:
151   friend task; 151   friend task;
152   union { std::exception_ptr ep_; }; 152   union { std::exception_ptr ep_; };
153   bool has_ep_; 153   bool has_ep_;
154   154  
155   public: 155   public:
156   /// Construct the promise with no stored exception. 156   /// Construct the promise with no stored exception.
HITCBC 157   2753 promise_type() noexcept 157   2772 promise_type() noexcept
HITCBC 158   2753 : has_ep_(false) 158   2772 : has_ep_(false)
159   { 159   {
HITCBC 160   2753 } 160   2772 }
161   161  
162   /// Destroy the promise, releasing any stored exception. 162   /// Destroy the promise, releasing any stored exception.
HITCBC 163   2753 ~promise_type() 163   2772 ~promise_type()
164   { 164   {
HITCBC 165   2753 if(has_ep_) 165   2772 if(has_ep_)
HITCBC 166   489 ep_.~exception_ptr(); 166   489 ep_.~exception_ptr();
HITCBC 167   2753 } 167   2772 }
168   168  
169   /** Return the exception captured by the coroutine body, if any. 169   /** Return the exception captured by the coroutine body, if any.
170   170  
171   @return The stored exception, or a null `std::exception_ptr` 171   @return The stored exception, or a null `std::exception_ptr`
172   if the coroutine did not exit via an unhandled exception. 172   if the coroutine did not exit via an unhandled exception.
173   */ 173   */
HITCBC 174   2151 std::exception_ptr exception() const noexcept 174   2164 std::exception_ptr exception() const noexcept
175   { 175   {
HITCBC 176   2151 if(has_ep_) 176   2164 if(has_ep_)
HITCBC 177   730 return ep_; 177   730 return ep_;
HITCBC 178   1421 return {}; 178   1434 return {};
179   } 179   }
180   180  
181   /** Return the owning `task` for this coroutine. 181   /** Return the owning `task` for this coroutine.
182   182  
183   Called by the compiler to produce the object returned to the 183   Called by the compiler to produce the object returned to the
184   caller when the coroutine is created. 184   caller when the coroutine is created.
185   185  
186   @return A `task` owning the coroutine frame. 186   @return A `task` owning the coroutine frame.
187   */ 187   */
HITCBC 188   2753 task get_return_object() 188   2772 task get_return_object()
189   { 189   {
HITCBC 190   2753 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 190   2772 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
191   } 191   }
192   192  
193   /** Return the initial-suspend awaiter. 193   /** Return the initial-suspend awaiter.
194   194  
195   The coroutine always suspends at the initial suspend point, 195   The coroutine always suspends at the initial suspend point,
196   so the body does not start until the task is awaited. When the 196   so the body does not start until the task is awaited. When the
197   body is resumed, the awaiter restores the thread-local frame 197   body is resumed, the awaiter restores the thread-local frame
198   allocator from the stored environment. 198   allocator from the stored environment.
199   199  
200   @return An awaiter that suspends unconditionally. 200   @return An awaiter that suspends unconditionally.
201   */ 201   */
HITCBC 202   2753 auto initial_suspend() noexcept 202   2772 auto initial_suspend() noexcept
203   { 203   {
204   struct awaiter 204   struct awaiter
205   { 205   {
206   promise_type* p_; 206   promise_type* p_;
207   207  
HITCBC 208   2753 bool await_ready() const noexcept 208   2772 bool await_ready() const noexcept
209   { 209   {
HITCBC 210   2753 return false; 210   2772 return false;
211   } 211   }
212   212  
HITCBC 213   2753 void await_suspend(std::coroutine_handle<>) const noexcept 213   2772 void await_suspend(std::coroutine_handle<>) const noexcept
214   { 214   {
HITCBC 215   2753 } 215   2772 }
216   216  
HITCBC 217   2749 void await_resume() const noexcept 217   2768 void await_resume() const noexcept
218   { 218   {
219   // Restore TLS when body starts executing 219   // Restore TLS when body starts executing
HITCBC 220   2749 set_current_frame_allocator(p_->environment()->frame_allocator); 220   2768 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 221   2749 } 221   2768 }
222   }; 222   };
HITCBC 223   2753 return awaiter{this}; 223   2772 return awaiter{this};
224   } 224   }
225   225  
226   /** Return the final-suspend awaiter. 226   /** Return the final-suspend awaiter.
227   227  
228   The coroutine always suspends at the final suspend point. The 228   The coroutine always suspends at the final suspend point. The
229   awaiter's `await_suspend` performs symmetric transfer to the 229   awaiter's `await_suspend` performs symmetric transfer to the
230   stored continuation (consuming it), resuming the awaiting 230   stored continuation (consuming it), resuming the awaiting
231   coroutine. 231   coroutine.
232   232  
233   @return An awaiter that suspends and transfers to the 233   @return An awaiter that suspends and transfers to the
234   continuation. 234   continuation.
235   */ 235   */
HITCBC 236   2610 auto final_suspend() noexcept 236   2623 auto final_suspend() noexcept
237   { 237   {
238   struct awaiter 238   struct awaiter
239   { 239   {
240   promise_type* p_; 240   promise_type* p_;
241   241  
HITCBC 242   2610 bool await_ready() const noexcept 242   2623 bool await_ready() const noexcept
243   { 243   {
HITCBC 244   2610 return false; 244   2623 return false;
245   } 245   }
246   246  
HITCBC 247   2610 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 247   2623 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
248   { 248   {
HITCBC 249   2610 return p_->continuation(); 249   2623 return p_->continuation();
250   } 250   }
251   251  
252   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 252   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
253   }; 253   };
HITCBC 254   2610 return awaiter{this}; 254   2623 return awaiter{this};
255   } 255   }
256   256  
257   /** Capture the in-flight exception from the coroutine body. 257   /** Capture the in-flight exception from the coroutine body.
258   258  
259   Called by the compiler when the coroutine body exits via an 259   Called by the compiler when the coroutine body exits via an
260   unhandled exception. The captured exception is rethrown when 260   unhandled exception. The captured exception is rethrown when
261   the task is awaited. 261   the task is awaited.
262   */ 262   */
HITCBC 263   489 void unhandled_exception() noexcept 263   489 void unhandled_exception() noexcept
264   { 264   {
HITCBC 265   489 new (&ep_) std::exception_ptr(std::current_exception()); 265   489 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 266   489 has_ep_ = true; 266   489 has_ep_ = true;
HITCBC 267   489 } 267   489 }
268   268  
269   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable. 269   /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
270   270  
271   Forwards the environment to the inner awaitable's 271   Forwards the environment to the inner awaitable's
272   environment-taking `await_suspend` and restores the 272   environment-taking `await_suspend` and restores the
273   thread-local frame allocator before the body resumes. 273   thread-local frame allocator before the body resumes.
274   274  
275   @tparam Awaitable The awaitable being transformed. 275   @tparam Awaitable The awaitable being transformed.
276   */ 276   */
277   template<class Awaitable> 277   template<class Awaitable>
278   struct transform_awaiter 278   struct transform_awaiter
279   { 279   {
280   /// The wrapped awaitable, decayed and stored by value. 280   /// The wrapped awaitable, decayed and stored by value.
281   std::decay_t<Awaitable> a_; 281   std::decay_t<Awaitable> a_;
282   282  
283   /// The promise of the coroutine performing the `co_await`. 283   /// The promise of the coroutine performing the `co_await`.
284   promise_type* p_; 284   promise_type* p_;
285   285  
286   /** Report whether the wrapped awaitable is already complete. 286   /** Report whether the wrapped awaitable is already complete.
287   287  
288   @return The wrapped awaitable's own `await_ready` result: 288   @return The wrapped awaitable's own `await_ready` result:
289   `true` if no suspension is needed. 289   `true` if no suspension is needed.
290   */ 290   */
HITCBC 291   2864 bool await_ready() noexcept 291   2883 bool await_ready() noexcept
292   { 292   {
HITCBC 293   2864 return a_.await_ready(); 293   2883 return a_.await_ready();
294   } 294   }
295   295  
296   /** Restore the frame allocator, then resume the wrapped 296   /** Restore the frame allocator, then resume the wrapped
297   awaitable. 297   awaitable.
298   298  
299   Reinstalls the thread-local frame allocator from the stored 299   Reinstalls the thread-local frame allocator from the stored
300   environment before the body continues. This is needed 300   environment before the body continues. This is needed
301   because the resumption may arrive on a different thread 301   because the resumption may arrive on a different thread
302   than the one that suspended. 302   than the one that suspended.
303   303  
304   @return The wrapped awaitable's await-result, forwarded 304   @return The wrapped awaitable's await-result, forwarded
305   unchanged. 305   unchanged.
306   */ 306   */
HITCBC 307   2725 decltype(auto) await_resume() 307   2738 decltype(auto) await_resume()
308   { 308   {
309   // Restore TLS before body resumes 309   // Restore TLS before body resumes
HITCBC 310   2725 set_current_frame_allocator(p_->environment()->frame_allocator); 310   2738 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 311   2725 return a_.await_resume(); 311   2738 return a_.await_resume();
312   } 312   }
313   313  
314   /** Suspend by calling the wrapped awaitable with the 314   /** Suspend by calling the wrapped awaitable with the
315   environment. 315   environment.
316   316  
317   This is the plain `await_suspend` the compiler calls for the 317   This is the plain `await_suspend` the compiler calls for the
318   nested `co_await`. It forwards to the wrapped awaitable's 318   nested `co_await`. It forwards to the wrapped awaitable's
319   @ref IoAwaitable overload, supplying the promise's stored 319   @ref IoAwaitable overload, supplying the promise's stored
320   environment as the second argument. It then hands back 320   environment as the second argument. It then hands back
321   that call's result unchanged, so the wrapped awaitable's 321   that call's result unchanged, so the wrapped awaitable's
322   suspension decision, whatever form it takes, is preserved. 322   suspension decision, whatever form it takes, is preserved.
323   323  
324   @param h The coroutine performing the `co_await`. 324   @param h The coroutine performing the `co_await`.
325   325  
326   @return Whatever the wrapped awaitable's `await_suspend` 326   @return Whatever the wrapped awaitable's `await_suspend`
327   returns. When that is a `std::coroutine_handle<>`, the 327   returns. When that is a `std::coroutine_handle<>`, the
328   handle is routed through `detail::symmetric_transfer`. 328   handle is routed through `detail::symmetric_transfer`.
329   On MSVC that helper resumes the handle on the current 329   On MSVC that helper resumes the handle on the current
330   stack, and this function returns `void`, so the awaiting 330   stack, and this function returns `void`, so the awaiting
331   coroutine suspends unconditionally. On every other 331   coroutine suspends unconditionally. On every other
332   compiler the handle is returned unchanged for symmetric 332   compiler the handle is returned unchanged for symmetric
333   transfer. 333   transfer.
334   */ 334   */
335   template<class Promise> 335   template<class Promise>
HITCBC 336   2245 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 336   2263 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
337   { 337   {
338   using R = decltype(a_.await_suspend(h, p_->environment())); 338   using R = decltype(a_.await_suspend(h, p_->environment()));
339   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 339   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 340   1245 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 340   1263 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
341   else 341   else
HITCBC 342   1000 return a_.await_suspend(h, p_->environment()); 342   1000 return a_.await_suspend(h, p_->environment());
343   } 343   }
344   }; 344   };
345   345  
346   /** Transform a nested awaitable before `co_await`. 346   /** Transform a nested awaitable before `co_await`.
347   347  
348   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the 348   Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
349   coroutine's environment is propagated into it. A diagnostic 349   coroutine's environment is propagated into it. A diagnostic
350   is emitted if the awaitable does not satisfy @ref IoAwaitable. 350   is emitted if the awaitable does not satisfy @ref IoAwaitable.
351   351  
352   @param a The awaitable expression from `co_await a`. 352   @param a The awaitable expression from `co_await a`.
353   353  
354   @return A @ref transform_awaiter wrapping `a`. 354   @return A @ref transform_awaiter wrapping `a`.
355   */ 355   */
356   template<class Awaitable> 356   template<class Awaitable>
HITCBC 357   2864 auto transform_awaitable(Awaitable&& a) 357   2883 auto transform_awaitable(Awaitable&& a)
358   { 358   {
359   using A = std::decay_t<Awaitable>; 359   using A = std::decay_t<Awaitable>;
360   if constexpr (IoAwaitable<A>) 360   if constexpr (IoAwaitable<A>)
361   { 361   {
362   return transform_awaiter<Awaitable>{ 362   return transform_awaiter<Awaitable>{
HITCBC 363   4382 std::forward<Awaitable>(a), this}; 363   4420 std::forward<Awaitable>(a), this};
364   } 364   }
365   else 365   else
366   { 366   {
367 - static_assert(sizeof(A) == 0, "requires IoAwaitable"); 367 + static_assert(IoAwaitable<A>, "requires IoAwaitable");
368   } 368   }
HITCBC 369   1518 } 369   1537 }
370   }; 370   };
371   371  
372   /** Handle to the owned coroutine frame. 372   /** Handle to the owned coroutine frame.
373   373  
374   Null when the task is empty (for example after a move or after 374   Null when the task is empty (for example after a move or after
375   @ref release). Prefer @ref handle to read this; the member is 375   @ref release). Prefer @ref handle to read this; the member is
376   public for use by the coroutine machinery. 376   public for use by the coroutine machinery.
377   */ 377   */
378   std::coroutine_handle<promise_type> h_; 378   std::coroutine_handle<promise_type> h_;
379   379  
380   /// Destroy the task and its coroutine frame if owned. 380   /// Destroy the task and its coroutine frame if owned.
HITCBC 381   5843 ~task() 381   5862 ~task()
382   { 382   {
HITCBC 383   5843 if(h_) 383   5862 if(h_)
HITCBC 384   767 h_.destroy(); 384   767 h_.destroy();
HITCBC 385   5843 } 385   5862 }
386   386  
387   /** Report whether the awaited task is already complete. 387   /** Report whether the awaited task is already complete.
388   388  
389   Always returns `false`; a task is lazy and has not started when 389   Always returns `false`; a task is lazy and has not started when
390   it is awaited, so the awaiting coroutine always suspends. 390   it is awaited, so the awaiting coroutine always suspends.
391   391  
392   @return `false`. 392   @return `false`.
393   */ 393   */
HITCBC 394   764 bool await_ready() const noexcept 394   764 bool await_ready() const noexcept
395   { 395   {
HITCBC 396   764 return false; 396   764 return false;
397   } 397   }
398   398  
399   /** Return the task's result, rethrowing any captured exception. 399   /** Return the task's result, rethrowing any captured exception.
400   400  
401   If the coroutine body exited via an unhandled exception, that 401   If the coroutine body exited via an unhandled exception, that
402   exception is rethrown here. Otherwise the result is returned by 402   exception is rethrown here. Otherwise the result is returned by
403   move (for `task<T>`) or nothing is returned (for `task<void>`). 403   move (for `task<T>`) or nothing is returned (for `task<void>`).
404   404  
405   @return The result value for non-void `T`; otherwise `void`. 405   @return The result value for non-void `T`; otherwise `void`.
406   406  
407   @throws The exception captured by the coroutine body, if any. 407   @throws The exception captured by the coroutine body, if any.
408   408  
409   @note Discarding an `io_result` silently drops the error 409   @note Discarding an `io_result` silently drops the error
410   code, so that overload is marked `[[nodiscard]]`. 410   code, so that overload is marked `[[nodiscard]]`.
411   */ 411   */
HITCBC 412   552 [[nodiscard]] auto await_resume() 412   552 [[nodiscard]] auto await_resume()
413   requires detail::is_io_result_v<T> 413   requires detail::is_io_result_v<T>
414   { 414   {
HITCBC 415   552 if(h_.promise().has_ep_) 415   552 if(h_.promise().has_ep_)
HITCBC 416   105 std::rethrow_exception(h_.promise().ep_); 416   105 std::rethrow_exception(h_.promise().ep_);
HITCBC 417   447 return std::move(*h_.promise().result_); 417   447 return std::move(*h_.promise().result_);
418   } 418   }
419   419  
HITCBC 420   211 auto await_resume() 420   211 auto await_resume()
421   requires (! detail::is_io_result_v<T>) 421   requires (! detail::is_io_result_v<T>)
422   { 422   {
HITCBC 423   211 if(h_.promise().has_ep_) 423   211 if(h_.promise().has_ep_)
HITCBC 424   18 std::rethrow_exception(h_.promise().ep_); 424   18 std::rethrow_exception(h_.promise().ep_);
425   if constexpr (! std::is_void_v<T>) 425   if constexpr (! std::is_void_v<T>)
HITCBC 426   148 return std::move(*h_.promise().result_); 426   148 return std::move(*h_.promise().result_);
427   else 427   else
HITCBC 428   45 return; 428   45 return;
429   } 429   }
430   430  
431   /** Start the task with the awaiting coroutine's context. 431   /** Start the task with the awaiting coroutine's context.
432   432  
433   Stores `cont` as the continuation to resume on completion. 433   Stores `cont` as the continuation to resume on completion.
434   Stores `env` as the execution environment propagated to nested 434   Stores `env` as the execution environment propagated to nested
435   `co_await` expressions. Then transfers control into the task's 435   `co_await` expressions. Then transfers control into the task's
436   coroutine body via the returned handle. 436   coroutine body via the returned handle.
437   437  
438   @param cont The awaiting coroutine to resume when the task 438   @param cont The awaiting coroutine to resume when the task
439   completes. 439   completes.
440   440  
441   @param env The execution environment (executor, stop token, and 441   @param env The execution environment (executor, stop token, and
442   frame allocator). It must outlive the task. 442   frame allocator). It must outlive the task.
443   443  
444   @return The task's coroutine handle, for symmetric transfer. 444   @return The task's coroutine handle, for symmetric transfer.
445   */ 445   */
HITCBC 446   683 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 446   683 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
447   { 447   {
HITCBC 448   683 h_.promise().set_continuation(cont); 448   683 h_.promise().set_continuation(cont);
HITCBC 449   683 h_.promise().set_environment(env); 449   683 h_.promise().set_environment(env);
HITCBC 450   683 return h_; 450   683 return h_;
451   } 451   }
452   452  
453   /** Return the coroutine handle. 453   /** Return the coroutine handle.
454   454  
455   @note Do not call `destroy()` on the returned handle while the 455   @note Do not call `destroy()` on the returned handle while the
456   task is being awaited. The task's lifetime is normally managed 456   task is being awaited. The task's lifetime is normally managed
457   by `run_async`, `run`, or the awaiting parent. Manually 457   by `run_async`, `run`, or the awaiting parent. Manually
458   destroying a suspended task that another coroutine is awaiting 458   destroying a suspended task that another coroutine is awaiting
459   produces undefined behavior. For cooperative cancellation, use 459   produces undefined behavior. For cooperative cancellation, use
460   `std::stop_token`. 460   `std::stop_token`.
461   461  
462   @return The coroutine handle. 462   @return The coroutine handle.
463   */ 463   */
HITCBC 464   2069 std::coroutine_handle<promise_type> handle() const noexcept 464   2088 std::coroutine_handle<promise_type> handle() const noexcept
465   { 465   {
HITCBC 466   2069 return h_; 466   2088 return h_;
467   } 467   }
468   468  
469   /** Release ownership of the coroutine frame. 469   /** Release ownership of the coroutine frame.
470   470  
471   After calling this, destroying the task does not destroy the 471   After calling this, destroying the task does not destroy the
472   coroutine frame. The caller becomes responsible for the frame's 472   coroutine frame. The caller becomes responsible for the frame's
473   lifetime. 473   lifetime.
474   474  
475   @note The caller may call `destroy()` on the released handle 475   @note The caller may call `destroy()` on the released handle
476   only when the task has not started or has fully completed. 476   only when the task has not started or has fully completed.
477   Destroying a suspended task that is being awaited produces 477   Destroying a suspended task that is being awaited produces
478   undefined behavior. 478   undefined behavior.
479   479  
480   @par Postconditions 480   @par Postconditions
481   `handle()` returns a null handle. Callers needing the 481   `handle()` returns a null handle. Callers needing the
482   original handle must save it, via @ref handle, before 482   original handle must save it, via @ref handle, before
483   calling this. 483   calling this.
484   */ 484   */
HITCBC 485   1986 void release() noexcept 485   2005 void release() noexcept
486   { 486   {
HITCBC 487   1986 h_ = nullptr; 487   2005 h_ = nullptr;
HITCBC 488   1986 } 488   2005 }
489   489  
490   /** Copy construction is disabled; a task uniquely owns its frame. 490   /** Copy construction is disabled; a task uniquely owns its frame.
491   491  
492   @param other The task that would be copied. 492   @param other The task that would be copied.
493   */ 493   */
494   task(task const& other) = delete; 494   task(task const& other) = delete;
495   495  
496   /** Copy assignment is disabled; a task uniquely owns its frame. 496   /** Copy assignment is disabled; a task uniquely owns its frame.
497   497  
498   @param other The task that would be assigned from. 498   @param other The task that would be assigned from.
499   499  
500   @return A reference to `*this`. 500   @return A reference to `*this`.
501   */ 501   */
502   task& operator=(task const& other) = delete; 502   task& operator=(task const& other) = delete;
503   503  
504   /** Construct by moving, transferring ownership of the frame. 504   /** Construct by moving, transferring ownership of the frame.
505   505  
506   @par Postconditions 506   @par Postconditions
507   `other` is empty and must not be awaited. 507   `other` is empty and must not be awaited.
508   508  
509   @param other The task to move from. 509   @param other The task to move from.
510   */ 510   */
HITCBC 511   3090 task(task&& other) noexcept 511   3090 task(task&& other) noexcept
HITCBC 512   3090 : h_(std::exchange(other.h_, nullptr)) 512   3090 : h_(std::exchange(other.h_, nullptr))
513   { 513   {
HITCBC 514   3090 } 514   3090 }
515   515  
516   /** Assign by moving, transferring ownership of the frame. 516   /** Assign by moving, transferring ownership of the frame.
517   517  
518   If this task already owns a coroutine frame, that frame is 518   If this task already owns a coroutine frame, that frame is
519   destroyed first. Self-assignment is a no-op. 519   destroyed first. Self-assignment is a no-op.
520   520  
521   @par Postconditions 521   @par Postconditions
522   `other` is empty and must not be awaited. 522   `other` is empty and must not be awaited.
523   523  
524   @param other The task to move from. 524   @param other The task to move from.
525   525  
526   @return A reference to `*this`. 526   @return A reference to `*this`.
527   */ 527   */
528   task& operator=(task&& other) noexcept 528   task& operator=(task&& other) noexcept
529   { 529   {
530   if(this != &other) 530   if(this != &other)
531   { 531   {
532   if(h_) 532   if(h_)
533   h_.destroy(); 533   h_.destroy();
534   h_ = std::exchange(other.h_, nullptr); 534   h_ = std::exchange(other.h_, nullptr);
535   } 535   }
536   return *this; 536   return *this;
537   } 537   }
538   538  
539   private: 539   private:
HITCBC 540   2753 explicit task(std::coroutine_handle<promise_type> h) 540   2772 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 541   2753 : h_(h) 541   2772 : h_(h)
542   { 542   {
HITCBC 543   2753 } 543   2772 }
544   }; 544   };
545   545  
546   } // namespace capy 546   } // namespace capy
547   } // namespace boost 547   } // namespace boost
548   548  
549   #endif 549   #endif