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   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_TASK_HPP 10   #ifndef BOOST_CAPY_TASK_HPP
11   #define BOOST_CAPY_TASK_HPP 11   #define BOOST_CAPY_TASK_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/concept/executor.hpp> 14   #include <boost/capy/concept/executor.hpp>
15   #include <boost/capy/concept/io_awaitable.hpp> 15   #include <boost/capy/concept/io_awaitable.hpp>
16   #include <boost/capy/ex/io_awaitable_promise_base.hpp> 16   #include <boost/capy/ex/io_awaitable_promise_base.hpp>
17   #include <boost/capy/ex/io_env.hpp> 17   #include <boost/capy/ex/io_env.hpp>
18   #include <boost/capy/ex/frame_allocator.hpp> 18   #include <boost/capy/ex/frame_allocator.hpp>
19   #include <boost/capy/detail/await_suspend_helper.hpp> 19   #include <boost/capy/detail/await_suspend_helper.hpp>
20   20  
21   #include <exception> 21   #include <exception>
22   #include <optional> 22   #include <optional>
23   #include <type_traits> 23   #include <type_traits>
24   #include <utility> 24   #include <utility>
25   #include <variant> 25   #include <variant>
26   26  
27   namespace boost { 27   namespace boost {
28   namespace capy { 28   namespace capy {
29   29  
30   namespace detail { 30   namespace detail {
31   31  
32   // Helper base for result storage and return_void/return_value 32   // Helper base for result storage and return_void/return_value
33   template<typename T> 33   template<typename T>
34   struct task_return_base 34   struct task_return_base
35   { 35   {
36   std::optional<T> result_; 36   std::optional<T> result_;
37   37  
HITCBC 38   1328 void return_value(T value) 38   1323 void return_value(T value)
39   { 39   {
HITCBC 40   1328 result_ = std::move(value); 40   1323 result_ = std::move(value);
HITCBC 41   1328 } 41   1323 }
42   42  
HITCBC 43   160 T&& result() noexcept 43   160 T&& result() noexcept
44   { 44   {
HITCBC 45   160 return std::move(*result_); 45   160 return std::move(*result_);
46   } 46   }
47   }; 47   };
48   48  
49   template<> 49   template<>
50   struct task_return_base<void> 50   struct task_return_base<void>
51   { 51   {
HITCBC 52   2024 void return_void() 52   1987 void return_void()
53   { 53   {
HITCBC 54   2024 } 54   1987 }
55   }; 55   };
56   56  
57   } // namespace detail 57   } // namespace detail
58   58  
59   /** Lazy coroutine task satisfying @ref IoRunnable. 59   /** Lazy coroutine task satisfying @ref IoRunnable.
60   60  
61   Use `task<T>` as the return type for coroutines that perform I/O 61   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 62   and return a value of type `T`. The coroutine body does not start
63   executing until the task is awaited, enabling efficient composition 63   executing until the task is awaited, enabling efficient composition
64   without unnecessary eager execution. 64   without unnecessary eager execution.
65   65  
66   The task participates in the I/O awaitable protocol: when awaited, 66   The task participates in the I/O awaitable protocol: when awaited,
67   it receives the caller's executor and stop token, propagating them 67   it receives the caller's executor and stop token, propagating them
68   to nested `co_await` expressions. This enables cancellation and 68   to nested `co_await` expressions. This enables cancellation and
69   proper completion dispatch across executor boundaries. 69   proper completion dispatch across executor boundaries.
70   70  
71   @par Thread Safety 71   @par Thread Safety
72   Distinct objects: Safe. 72   Distinct objects: Safe.
73   Shared objects: Unsafe. 73   Shared objects: Unsafe.
74   74  
75   @par Example 75   @par Example
76   76  
77   @code 77   @code
78   task<int> compute_value() 78   task<int> compute_value()
79   { 79   {
80   auto [ec, n] = co_await stream.read_some( buf ); 80   auto [ec, n] = co_await stream.read_some( buf );
81   if( ec ) 81   if( ec )
82   co_return 0; 82   co_return 0;
83   co_return process( buf, n ); 83   co_return process( buf, n );
84   } 84   }
85   85  
86   task<> run_session( tcp_socket sock ) 86   task<> run_session( tcp_socket sock )
87   { 87   {
88   int result = co_await compute_value(); 88   int result = co_await compute_value();
89   // ... 89   // ...
90   } 90   }
91   @endcode 91   @endcode
92   92  
93   @tparam T The result type. Use `task<>` for `task<void>`. 93   @tparam T The result type. Use `task<>` for `task<void>`.
94   94  
95   @see IoRunnable, IoAwaitable, run, run_async 95   @see IoRunnable, IoAwaitable, run, run_async
96   */ 96   */
97   template<typename T = void> 97   template<typename T = void>
98   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 98   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
99   task 99   task
100   { 100   {
  101 + /** The coroutine promise type for `task<T>`.
  102 +
  103 + This is the promise object the compiler associates with a
  104 + `task<T>` coroutine. It satisfies the coroutine promise
  105 + requirements and participates in the I/O awaitable protocol via
  106 + @ref io_awaitable_promise_base. It is part of the coroutine
  107 + machinery and is not intended to be used directly by callers.
  108 +
  109 + Result storage and `return_value`/`return_void` are provided by
  110 + `detail::task_return_base<T>`.
  111 +
  112 + @see io_awaitable_promise_base, IoRunnable
  113 + */
101   struct promise_type 114   struct promise_type
102   : io_awaitable_promise_base<promise_type> 115   : io_awaitable_promise_base<promise_type>
103   , detail::task_return_base<T> 116   , detail::task_return_base<T>
104   { 117   {
105   private: 118   private:
106   friend task; 119   friend task;
107   union { std::exception_ptr ep_; }; 120   union { std::exception_ptr ep_; };
108   bool has_ep_; 121   bool has_ep_;
109   122  
110   public: 123   public:
  124 + /// Construct the promise with no stored exception.
HITCBC 111   5141 promise_type() noexcept 125   5108 promise_type() noexcept
HITCBC 112   5141 : has_ep_(false) 126   5108 : has_ep_(false)
113   { 127   {
HITCBC 114   5141 } 128   5108 }
115   129  
  130 + /// Destroy the promise, releasing any stored exception.
HITCBC 116   5141 ~promise_type() 131   5108 ~promise_type()
117   { 132   {
HITCBC 118   5141 if(has_ep_) 133   5108 if(has_ep_)
HITCBC 119   1618 ep_.~exception_ptr(); 134   1612 ep_.~exception_ptr();
HITCBC 120   5141 } 135   5108 }
121   136  
  137 + /** Return the exception captured by the coroutine body, if any.
  138 +
  139 + @return The stored exception, or a null `std::exception_ptr`
  140 + if the coroutine did not exit via an unhandled exception.
  141 + */
HITCBC 122   4224 std::exception_ptr exception() const noexcept 142   4182 std::exception_ptr exception() const noexcept
123   { 143   {
HITCBC 124   4224 if(has_ep_) 144   4182 if(has_ep_)
HITCBC 125   2114 return ep_; 145   2108 return ep_;
HITCBC 126   2110 return {}; 146   2074 return {};
127   } 147   }
128   148  
  149 + /** Return the owning `task` for this coroutine.
  150 +
  151 + Called by the compiler to produce the object returned to the
  152 + caller when the coroutine is created.
  153 +
  154 + @return A `task` owning the coroutine frame.
  155 + */
HITCBC 129   5141 task get_return_object() 156   5108 task get_return_object()
130   { 157   {
HITCBC 131   5141 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 158   5108 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132   } 159   }
133   160  
  161 + /** Return the initial-suspend awaiter.
  162 +
  163 + The coroutine always suspends at the initial suspend point,
  164 + so the body does not start until the task is awaited. When the
  165 + body is resumed, the awaiter restores the thread-local frame
  166 + allocator from the stored environment.
  167 +
  168 + @return An awaiter that suspends unconditionally.
  169 + */
HITCBC 134   5141 auto initial_suspend() noexcept 170   5108 auto initial_suspend() noexcept
135   { 171   {
136   struct awaiter 172   struct awaiter
137   { 173   {
138   promise_type* p_; 174   promise_type* p_;
139   175  
HITCBC 140   5141 bool await_ready() const noexcept 176   5108 bool await_ready() const noexcept
141   { 177   {
HITCBC 142   5141 return false; 178   5108 return false;
143   } 179   }
144   180  
HITCBC 145   5141 void await_suspend(std::coroutine_handle<>) const noexcept 181   5108 void await_suspend(std::coroutine_handle<>) const noexcept
146   { 182   {
HITCBC 147   5141 } 183   5108 }
148   184  
HITCBC 149   5138 void await_resume() const noexcept 185   5100 void await_resume() const noexcept
150   { 186   {
151   // Restore TLS when body starts executing 187   // Restore TLS when body starts executing
HITCBC 152   5138 set_current_frame_allocator(p_->environment()->frame_allocator); 188   5100 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 153   5138 } 189   5100 }
154   }; 190   };
HITCBC 155   5141 return awaiter{this}; 191   5108 return awaiter{this};
156   } 192   }
157   193  
  194 + /** Return the final-suspend awaiter.
  195 +
  196 + The coroutine always suspends at the final suspend point. The
  197 + awaiter's `await_suspend` performs symmetric transfer to the
  198 + stored continuation (consuming it), resuming the awaiting
  199 + coroutine.
  200 +
  201 + @return An awaiter that suspends and transfers to the
  202 + continuation.
  203 + */
HITCBC 158   4970 auto final_suspend() noexcept 204   4922 auto final_suspend() noexcept
159   { 205   {
160   struct awaiter 206   struct awaiter
161   { 207   {
162   promise_type* p_; 208   promise_type* p_;
163   209  
HITCBC 164   4970 bool await_ready() const noexcept 210   4922 bool await_ready() const noexcept
165   { 211   {
HITCBC 166   4970 return false; 212   4922 return false;
167   } 213   }
168   214  
HITCBC 169   4970 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 215   4922 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
170   { 216   {
HITCBC 171   4970 return p_->continuation(); 217   4922 return p_->continuation();
172   } 218   }
173   219  
174   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 220   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
175   }; 221   };
HITCBC 176   4970 return awaiter{this}; 222   4922 return awaiter{this};
177   } 223   }
178   224  
  225 + /** Capture the in-flight exception from the coroutine body.
  226 +
  227 + Called by the compiler when the coroutine body exits via an
  228 + unhandled exception. The captured exception is rethrown when
  229 + the task is awaited.
  230 + */
HITCBC 179   1618 void unhandled_exception() noexcept 231   1612 void unhandled_exception() noexcept
180   { 232   {
HITCBC 181   1618 new (&ep_) std::exception_ptr(std::current_exception()); 233   1612 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 182   1618 has_ep_ = true; 234   1612 has_ep_ = true;
HITCBC 183   1618 } 235   1612 }
184   236  
  237 + /** Awaiter wrapping a nested `co_await` of an @ref IoAwaitable.
  238 +
  239 + Forwards the environment to the inner awaitable's
  240 + environment-taking `await_suspend` and restores the
  241 + thread-local frame allocator before the body resumes.
  242 +
  243 + @tparam Awaitable The awaitable being transformed.
  244 + */
185   template<class Awaitable> 245   template<class Awaitable>
186   struct transform_awaiter 246   struct transform_awaiter
187   { 247   {
188   std::decay_t<Awaitable> a_; 248   std::decay_t<Awaitable> a_;
189   promise_type* p_; 249   promise_type* p_;
190   250  
HITCBC 191   9302 bool await_ready() noexcept 251   9260 bool await_ready() noexcept
192   { 252   {
HITCBC 193   9302 return a_.await_ready(); 253   9260 return a_.await_ready();
194   } 254   }
195   255  
HITCBC 196   9134 decltype(auto) await_resume() 256   9082 decltype(auto) await_resume()
197   { 257   {
198   // Restore TLS before body resumes 258   // Restore TLS before body resumes
HITCBC 199   9134 set_current_frame_allocator(p_->environment()->frame_allocator); 259   9082 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 200   9134 return a_.await_resume(); 260   9082 return a_.await_resume();
201   } 261   }
202   262  
203   template<class Promise> 263   template<class Promise>
HITCBC 204   8010 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 264   2545 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
205   { 265   {
206   using R = decltype(a_.await_suspend(h, p_->environment())); 266   using R = decltype(a_.await_suspend(h, p_->environment()));
207   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 267   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 208   2913 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 268   2544 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
209   else 269   else
HITCBC 210   5097 return a_.await_suspend(h, p_->environment()); 270   1 return a_.await_suspend(h, p_->environment());
211   } 271   }
212   }; 272   };
213   273  
  274 + /** Transform a nested awaitable before `co_await`.
  275 +
  276 + Wraps an @ref IoAwaitable in a @ref transform_awaiter so the
  277 + coroutine's environment is propagated into it. A diagnostic
  278 + is emitted if the awaitable does not satisfy @ref IoAwaitable.
  279 +
  280 + @param a The awaitable expression from `co_await a`.
  281 +
  282 + @return A @ref transform_awaiter wrapping `a`.
  283 + */
214   template<class Awaitable> 284   template<class Awaitable>
HITCBC 215   9302 auto transform_awaitable(Awaitable&& a) 285   9260 auto transform_awaitable(Awaitable&& a)
216   { 286   {
217   using A = std::decay_t<Awaitable>; 287   using A = std::decay_t<Awaitable>;
218   if constexpr (IoAwaitable<A>) 288   if constexpr (IoAwaitable<A>)
219   { 289   {
220   return transform_awaiter<Awaitable>{ 290   return transform_awaiter<Awaitable>{
HITCBC 221   11792 std::forward<Awaitable>(a), this}; 291   11458 std::forward<Awaitable>(a), this};
222   } 292   }
223   else 293   else
224   { 294   {
225   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 295   static_assert(sizeof(A) == 0, "requires IoAwaitable");
226   } 296   }
HITCBC 227   2490 } 297   2198 }
228   }; 298   };
229   299  
  300 + /** Handle to the owned coroutine frame.
  301 +
  302 + 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
  304 + public for use by the coroutine machinery.
  305 + */
230   std::coroutine_handle<promise_type> h_; 306   std::coroutine_handle<promise_type> h_;
231   307  
232   /// Destroy the task and its coroutine frame if owned. 308   /// Destroy the task and its coroutine frame if owned.
HITCBC 233   10638 ~task() 309   10575 ~task()
234   { 310   {
HITCBC 235   10638 if(h_) 311   10575 if(h_)
HITCBC 236   1745 h_.destroy(); 312   1737 h_.destroy();
HITCBC 237   10638 } 313   10575 }
238   314  
239 - /// Return false; tasks are never immediately ready. 315 + /** Report whether the awaited task is already complete.
  316 +
  317 + Always returns `false`; a task is lazy and has not started when
  318 + it is awaited, so the awaiting coroutine always suspends.
  319 +
  320 + @return `false`.
  321 + */
HITCBC 240   1599 bool await_ready() const noexcept 322   1599 bool await_ready() const noexcept
241   { 323   {
HITCBC 242   1599 return false; 324   1599 return false;
243   } 325   }
244   326  
245 - /// Return the result or rethrow any stored exception. 327 + /** Return the task's result, rethrowing any captured exception.
  328 +
  329 + If the coroutine body exited via an unhandled exception, that
  330 + exception is rethrown here. Otherwise the result is returned by
  331 + move (for `task<T>`) or nothing is returned (for `task<void>`).
  332 +
  333 + @return The result value for non-void `T`; otherwise `void`.
  334 +
  335 + @throws The exception captured by the coroutine body, if any.
  336 + */
HITCBC 246   1742 auto await_resume() 337   1734 auto await_resume()
247   { 338   {
HITCBC 248   1742 if(h_.promise().has_ep_) 339   1734 if(h_.promise().has_ep_)
HITCBC 249   560 std::rethrow_exception(h_.promise().ep_); 340   557 std::rethrow_exception(h_.promise().ep_);
250   if constexpr (! std::is_void_v<T>) 341   if constexpr (! std::is_void_v<T>)
HITCBC 251   1166 return std::move(*h_.promise().result_); 342   1161 return std::move(*h_.promise().result_);
252   else 343   else
HITCBC 253   16 return; 344   16 return;
254   } 345   }
255   346  
256 - /// Start execution with the caller's context. 347 + /** Start the task with the awaiting coroutine's context.
  348 +
  349 + Stores `cont` as the continuation to resume on completion and
  350 + `env` as the execution environment propagated to nested
  351 + `co_await` expressions, then transfers control into the task's
  352 + coroutine body via the returned handle.
  353 +
  354 + @param cont The awaiting coroutine to resume when the task
  355 + completes.
  356 +
  357 + @param env The execution environment (executor, stop token, and
  358 + frame allocator). It must outlive the task.
  359 +
  360 + @return The task's coroutine handle, for symmetric transfer.
  361 + */
HITCBC 257   1720 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 362   1712 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
258   { 363   {
HITCBC 259   1720 h_.promise().set_continuation(cont); 364   1712 h_.promise().set_continuation(cont);
HITCBC 260   1720 h_.promise().set_environment(env); 365   1712 h_.promise().set_environment(env);
HITCBC 261   1720 return h_; 366   1712 return h_;
262   } 367   }
263   368  
264   /** Return the coroutine handle. 369   /** Return the coroutine handle.
265   370  
266   @note Do not call `destroy()` on the returned handle while the 371   @note Do not call `destroy()` on the returned handle while the
267   task is being awaited. The task's lifetime is normally managed 372   task is being awaited. The task's lifetime is normally managed
268   by `run_async`, `run`, or the awaiting parent; manually 373   by `run_async`, `run`, or the awaiting parent; manually
269   destroying a suspended task that another coroutine is awaiting 374   destroying a suspended task that another coroutine is awaiting
270   produces undefined behavior. For cooperative cancellation, use 375   produces undefined behavior. For cooperative cancellation, use
271   `std::stop_token`. 376   `std::stop_token`.
272   377  
273   @return The coroutine handle. 378   @return The coroutine handle.
274   */ 379   */
HITCBC 275   3421 std::coroutine_handle<promise_type> handle() const noexcept 380   3396 std::coroutine_handle<promise_type> handle() const noexcept
276   { 381   {
HITCBC 277   3421 return h_; 382   3396 return h_;
278   } 383   }
279   384  
280   /** Release ownership of the coroutine frame. 385   /** Release ownership of the coroutine frame.
281   386  
282   After calling this, destroying the task does not destroy the 387   After calling this, destroying the task does not destroy the
283   coroutine frame. The caller becomes responsible for the frame's 388   coroutine frame. The caller becomes responsible for the frame's
284   lifetime. 389   lifetime.
285   390  
286   @note If the caller intends to call `destroy()` on the 391   @note If the caller intends to call `destroy()` on the
287   released handle, it must do so only when the task has not 392   released handle, it must do so only when the task has not
288   started or has fully completed. Destroying a suspended task 393   started or has fully completed. Destroying a suspended task
289   that is being awaited produces undefined behavior. 394   that is being awaited produces undefined behavior.
290   395  
291   @par Postconditions 396   @par Postconditions
292   `handle()` returns the original handle, but the task no longer 397   `handle()` returns the original handle, but the task no longer
293   owns it. 398   owns it.
294   */ 399   */
HITCBC 295   3396 void release() noexcept 400   3371 void release() noexcept
296   { 401   {
HITCBC 297   3396 h_ = nullptr; 402   3371 h_ = nullptr;
HITCBC 298   3396 } 403   3371 }
299   404  
300   task(task const&) = delete; 405   task(task const&) = delete;
301   task& operator=(task const&) = delete; 406   task& operator=(task const&) = delete;
302   407  
303 - /// Construct by moving, transferring ownership. 408 + /** Construct by moving, transferring ownership of the frame.
  409 +
  410 + @par Postconditions
  411 + `other` is empty and must not be awaited.
  412 +
  413 + @param other The task to move from.
  414 + */
HITCBC 304   5497 task(task&& other) noexcept 415   5467 task(task&& other) noexcept
HITCBC 305   5497 : h_(std::exchange(other.h_, nullptr)) 416   5467 : h_(std::exchange(other.h_, nullptr))
306   { 417   {
HITCBC 307   5497 } 418   5467 }
308   419  
309 - /// Assign by moving, transferring ownership. 420 + /** Assign by moving, transferring ownership of the frame.
  421 +
  422 + If this task already owns a coroutine frame, that frame is
  423 + destroyed first. Self-assignment is a no-op.
  424 +
  425 + @par Postconditions
  426 + `other` is empty and must not be awaited.
  427 +
  428 + @param other The task to move from.
  429 +
  430 + @return `*this`.
  431 + */
310   task& operator=(task&& other) noexcept 432   task& operator=(task&& other) noexcept
311   { 433   {
312   if(this != &other) 434   if(this != &other)
313   { 435   {
314   if(h_) 436   if(h_)
315   h_.destroy(); 437   h_.destroy();
316   h_ = std::exchange(other.h_, nullptr); 438   h_ = std::exchange(other.h_, nullptr);
317   } 439   }
318   return *this; 440   return *this;
319   } 441   }
320   442  
321   private: 443   private:
HITCBC 322   5141 explicit task(std::coroutine_handle<promise_type> h) 444   5108 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 323   5141 : h_(h) 445   5108 : h_(h)
324   { 446   {
HITCBC 325   5141 } 447   5108 }
326   }; 448   };
327   449  
328   } // namespace capy 450   } // namespace capy
329   } // namespace boost 451   } // namespace boost
330   452  
331   #endif 453   #endif