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