96.15% Lines (75/78) 96.43% Functions (27/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   // 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/corosio 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   20  
20   #include <exception> 21   #include <exception>
21   #include <optional> 22   #include <optional>
22   #include <type_traits> 23   #include <type_traits>
23   #include <utility> 24   #include <utility>
24   #include <variant> 25   #include <variant>
25   26  
26   namespace boost { 27   namespace boost {
27   namespace capy { 28   namespace capy {
28   29  
29   namespace detail { 30   namespace detail {
30   31  
31   // Helper base for result storage and return_void/return_value 32   // Helper base for result storage and return_void/return_value
32   template<typename T> 33   template<typename T>
33   struct task_return_base 34   struct task_return_base
34   { 35   {
35   std::optional<T> result_; 36   std::optional<T> result_;
36   37  
HITCBC 37   1259 void return_value(T value) 38   1283 void return_value(T value)
38   { 39   {
HITCBC 39   1259 result_ = std::move(value); 40   1283 result_ = std::move(value);
HITCBC 40   1259 } 41   1283 }
41   42  
HITCBC 42   143 T&& result() noexcept 43   148 T&& result() noexcept
43   { 44   {
HITCBC 44   143 return std::move(*result_); 45   148 return std::move(*result_);
45   } 46   }
46   }; 47   };
47   48  
48   template<> 49   template<>
49   struct task_return_base<void> 50   struct task_return_base<void>
50   { 51   {
HITCBC 51   1865 void return_void() 52   2008 void return_void()
52   { 53   {
HITCBC 53   1865 } 54   2008 }
54   }; 55   };
55   56  
56   } // namespace detail 57   } // namespace detail
57   58  
58   /** Lazy coroutine task satisfying @ref IoRunnable. 59   /** Lazy coroutine task satisfying @ref IoRunnable.
59   60  
60   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
61   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
62   executing until the task is awaited, enabling efficient composition 63   executing until the task is awaited, enabling efficient composition
63   without unnecessary eager execution. 64   without unnecessary eager execution.
64   65  
65   The task participates in the I/O awaitable protocol: when awaited, 66   The task participates in the I/O awaitable protocol: when awaited,
66   it receives the caller's executor and stop token, propagating them 67   it receives the caller's executor and stop token, propagating them
67   to nested `co_await` expressions. This enables cancellation and 68   to nested `co_await` expressions. This enables cancellation and
68   proper completion dispatch across executor boundaries. 69   proper completion dispatch across executor boundaries.
69 - @tparam T The result type. Use `task<>` for `task<void>`.  
70 -  
71   70  
72   @par Thread Safety 71   @par Thread Safety
73   Distinct objects: Safe. 72   Distinct objects: Safe.
74   Shared objects: Unsafe. 73   Shared objects: Unsafe.
75   74  
76   @par Example 75   @par Example
77   76  
78   @code 77   @code
79   task<int> compute_value() 78   task<int> compute_value()
80   { 79   {
81   auto [ec, n] = co_await stream.read_some( buf ); 80   auto [ec, n] = co_await stream.read_some( buf );
82   if( ec ) 81   if( ec )
83   co_return 0; 82   co_return 0;
84   co_return process( buf, n ); 83   co_return process( buf, n );
85   } 84   }
86   85  
87   task<> run_session( tcp_socket sock ) 86   task<> run_session( tcp_socket sock )
88   { 87   {
89   int result = co_await compute_value(); 88   int result = co_await compute_value();
90   // ... 89   // ...
91   } 90   }
92   @endcode 91   @endcode
93   92  
  93 + @tparam T The result type. Use `task<>` for `task<void>`.
  94 +
94   @see IoRunnable, IoAwaitable, run, run_async 95   @see IoRunnable, IoAwaitable, run, run_async
95   */ 96   */
96   template<typename T = void> 97   template<typename T = void>
97   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 98   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
98   task 99   task
99   { 100   {
100   struct promise_type 101   struct promise_type
101   : io_awaitable_promise_base<promise_type> 102   : io_awaitable_promise_base<promise_type>
102   , detail::task_return_base<T> 103   , detail::task_return_base<T>
103   { 104   {
104   private: 105   private:
105   friend task; 106   friend task;
106   union { std::exception_ptr ep_; }; 107   union { std::exception_ptr ep_; };
107   bool has_ep_; 108   bool has_ep_;
108   109  
109   public: 110   public:
HITCBC 110   4699 promise_type() noexcept 111   5041 promise_type() noexcept
HITCBC 111   4699 : has_ep_(false) 112   5041 : has_ep_(false)
112   { 113   {
HITCBC 113   4699 } 114   5041 }
114   115  
HITCBC 115   4699 ~promise_type() 116   5041 ~promise_type()
116   { 117   {
HITCBC 117   4699 if(has_ep_) 118   5041 if(has_ep_)
HITCBC 118   1567 ep_.~exception_ptr(); 119   1599 ep_.~exception_ptr();
HITCBC 119   4699 } 120   5041 }
120   121  
HITCBC 121   3931 std::exception_ptr exception() const noexcept 122   4180 std::exception_ptr exception() const noexcept
122   { 123   {
HITCBC 123   3931 if(has_ep_) 124   4180 if(has_ep_)
HITCBC 124   2058 return ep_; 125   2094 return ep_;
HITCBC 125   1873 return {}; 126   2086 return {};
126   } 127   }
127   128  
HITCBC 128   4699 task get_return_object() 129   5041 task get_return_object()
129   { 130   {
HITCBC 130   4699 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 131   5041 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131   } 132   }
132   133  
HITCBC 133   4699 auto initial_suspend() noexcept 134   5041 auto initial_suspend() noexcept
134   { 135   {
135   struct awaiter 136   struct awaiter
136   { 137   {
137   promise_type* p_; 138   promise_type* p_;
138   139  
HITCBC 139   4699 bool await_ready() const noexcept 140   5041 bool await_ready() const noexcept
140   { 141   {
HITCBC 141   4699 return false; 142   5041 return false;
142   } 143   }
143   144  
HITCBC 144   4699 void await_suspend(std::coroutine_handle<>) const noexcept 145   5041 void await_suspend(std::coroutine_handle<>) const noexcept
145   { 146   {
HITCBC 146   4699 } 147   5041 }
147   148  
HITCBC 148   4696 void await_resume() const noexcept 149   5038 void await_resume() const noexcept
149   { 150   {
150   // Restore TLS when body starts executing 151   // Restore TLS when body starts executing
HITCBC 151   4696 set_current_frame_allocator(p_->environment()->frame_allocator); 152   5038 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 152   4696 } 153   5038 }
153   }; 154   };
HITCBC 154   4699 return awaiter{this}; 155   5041 return awaiter{this};
155   } 156   }
156   157  
HITCBC 157   4691 auto final_suspend() noexcept 158   4890 auto final_suspend() noexcept
158   { 159   {
159   struct awaiter 160   struct awaiter
160   { 161   {
161   promise_type* p_; 162   promise_type* p_;
162   163  
HITCBC 163   4691 bool await_ready() const noexcept 164   4890 bool await_ready() const noexcept
164   { 165   {
HITCBC 165   4691 return false; 166   4890 return false;
166   } 167   }
167   168  
HITCBC 168   4691 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 169   4890 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
169   { 170   {
HITCBC 170   4691 return p_->continuation(); 171   4890 return p_->continuation();
171   } 172   }
172   173  
MISUBC 173   void await_resume() const noexcept 174   void await_resume() const noexcept
174   { 175   {
MISUBC 175   } 176   }
176   }; 177   };
HITCBC 177   4691 return awaiter{this}; 178   4890 return awaiter{this};
178   } 179   }
179   180  
HITCBC 180 - 1567 void unhandled_exception() 181 + 1599 void unhandled_exception() noexcept
181   { 182   {
HITCBC 182   1567 new (&ep_) std::exception_ptr(std::current_exception()); 183   1599 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 183   1567 has_ep_ = true; 184   1599 has_ep_ = true;
HITCBC 184   1567 } 185   1599 }
185   186  
186   template<class Awaitable> 187   template<class Awaitable>
187   struct transform_awaiter 188   struct transform_awaiter
188   { 189   {
189   std::decay_t<Awaitable> a_; 190   std::decay_t<Awaitable> a_;
190   promise_type* p_; 191   promise_type* p_;
191   192  
HITCBC 192   8603 bool await_ready() noexcept 193   9185 bool await_ready() noexcept
193   { 194   {
HITCBC 194   8603 return a_.await_ready(); 195   9185 return a_.await_ready();
195   } 196   }
196   197  
HITCBC 197   8598 decltype(auto) await_resume() 198   9037 decltype(auto) await_resume()
198   { 199   {
199   // Restore TLS before body resumes 200   // Restore TLS before body resumes
HITCBC 200   8598 set_current_frame_allocator(p_->environment()->frame_allocator); 201   9037 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 201   8598 return a_.await_resume(); 202   9037 return a_.await_resume();
202   } 203   }
203   204  
204   template<class Promise> 205   template<class Promise>
HITCBC 205   2223 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 206   2497 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
206   { 207   {
ECB 207 - 2223 return a_.await_suspend(h, p_->environment()); 208 + using R = decltype(a_.await_suspend(h, p_->environment()));
  209 + if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITGNC   210 + 2497 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
  211 + else
MISUNC   212 + return a_.await_suspend(h, p_->environment());
208   } 213   }
209   }; 214   };
210   215  
211   template<class Awaitable> 216   template<class Awaitable>
HITCBC 212   8603 auto transform_awaitable(Awaitable&& a) 217   9185 auto transform_awaitable(Awaitable&& a)
213   { 218   {
214   using A = std::decay_t<Awaitable>; 219   using A = std::decay_t<Awaitable>;
215   if constexpr (IoAwaitable<A>) 220   if constexpr (IoAwaitable<A>)
216   { 221   {
217   return transform_awaiter<Awaitable>{ 222   return transform_awaiter<Awaitable>{
HITCBC 218   10453 std::forward<Awaitable>(a), this}; 223   11354 std::forward<Awaitable>(a), this};
219   } 224   }
220   else 225   else
221   { 226   {
222   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 227   static_assert(sizeof(A) == 0, "requires IoAwaitable");
223   } 228   }
HITCBC 224   1850 } 229   2169 }
225   }; 230   };
226   231  
227   std::coroutine_handle<promise_type> h_; 232   std::coroutine_handle<promise_type> h_;
228   233  
229   /// Destroy the task and its coroutine frame if owned. 234   /// Destroy the task and its coroutine frame if owned.
HITCBC 230   10336 ~task() 235   10419 ~task()
231   { 236   {
HITCBC 232   10336 if(h_) 237   10419 if(h_)
HITCBC 233   1735 h_.destroy(); 238   1703 h_.destroy();
HITCBC 234   10336 } 239   10419 }
235   240  
236   /// Return false; tasks are never immediately ready. 241   /// Return false; tasks are never immediately ready.
HITCBC 237   1607 bool await_ready() const noexcept 242   1575 bool await_ready() const noexcept
238   { 243   {
HITCBC 239   1607 return false; 244   1575 return false;
240   } 245   }
241   246  
242   /// Return the result or rethrow any stored exception. 247   /// Return the result or rethrow any stored exception.
HITCBC 243   1732 auto await_resume() 248   1700 auto await_resume()
244   { 249   {
HITCBC 245   1732 if(h_.promise().has_ep_) 250   1700 if(h_.promise().has_ep_)
HITCBC 246   537 std::rethrow_exception(h_.promise().ep_); 251   551 std::rethrow_exception(h_.promise().ep_);
247   if constexpr (! std::is_void_v<T>) 252   if constexpr (! std::is_void_v<T>)
HITCBC 248   1114 return std::move(*h_.promise().result_); 253   1133 return std::move(*h_.promise().result_);
249   else 254   else
HITCBC 250   81 return; 255   16 return;
251   } 256   }
252   257  
253   /// Start execution with the caller's context. 258   /// Start execution with the caller's context.
HITCBC 254   1719 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 259   1678 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
255   { 260   {
HITCBC 256   1719 h_.promise().set_continuation(cont); 261   1678 h_.promise().set_continuation(cont);
HITCBC 257   1719 h_.promise().set_environment(env); 262   1678 h_.promise().set_environment(env);
HITCBC 258   1719 return h_; 263   1678 return h_;
259   } 264   }
260   265  
261 - /// Return the coroutine handle. 266 + /** Return the coroutine handle.
  267 +
  268 + @note Do not call `destroy()` on the returned handle while the
  269 + task is being awaited. The task's lifetime is normally managed
  270 + by `run_async`, `run`, or the awaiting parent; manually
  271 + destroying a suspended task that another coroutine is awaiting
  272 + produces undefined behavior. For cooperative cancellation, use
  273 + `std::stop_token`.
  274 +
  275 + @return The coroutine handle.
  276 + */
HITCBC 262   2980 std::coroutine_handle<promise_type> handle() const noexcept 277   3363 std::coroutine_handle<promise_type> handle() const noexcept
263   { 278   {
HITCBC 264   2980 return h_; 279   3363 return h_;
265   } 280   }
266   281  
267   /** Release ownership of the coroutine frame. 282   /** Release ownership of the coroutine frame.
268   283  
269   After calling this, destroying the task does not destroy the 284   After calling this, destroying the task does not destroy the
270   coroutine frame. The caller becomes responsible for the frame's 285   coroutine frame. The caller becomes responsible for the frame's
271   lifetime. 286   lifetime.
272   287  
  288 + @note If the caller intends to call `destroy()` on the
  289 + released handle, it must do so only when the task has not
  290 + started or has fully completed. Destroying a suspended task
  291 + that is being awaited produces undefined behavior.
  292 +
273   @par Postconditions 293   @par Postconditions
274   `handle()` returns the original handle, but the task no longer 294   `handle()` returns the original handle, but the task no longer
275   owns it. 295   owns it.
276   */ 296   */
HITCBC 277   2964 void release() noexcept 297   3338 void release() noexcept
278   { 298   {
HITCBC 279   2964 h_ = nullptr; 299   3338 h_ = nullptr;
HITCBC 280   2964 } 300   3338 }
281   301  
282   task(task const&) = delete; 302   task(task const&) = delete;
283   task& operator=(task const&) = delete; 303   task& operator=(task const&) = delete;
284   304  
285 - /// Move construct, transferring ownership. 305 + /// Construct by moving, transferring ownership.
HITCBC 286   5637 task(task&& other) noexcept 306   5378 task(task&& other) noexcept
HITCBC 287   5637 : h_(std::exchange(other.h_, nullptr)) 307   5378 : h_(std::exchange(other.h_, nullptr))
288   { 308   {
HITCBC 289   5637 } 309   5378 }
290   310  
291 - /// Move assign, transferring ownership. 311 + /// Assign by moving, transferring ownership.
292   task& operator=(task&& other) noexcept 312   task& operator=(task&& other) noexcept
293   { 313   {
294   if(this != &other) 314   if(this != &other)
295   { 315   {
296   if(h_) 316   if(h_)
297   h_.destroy(); 317   h_.destroy();
298   h_ = std::exchange(other.h_, nullptr); 318   h_ = std::exchange(other.h_, nullptr);
299   } 319   }
300   return *this; 320   return *this;
301   } 321   }
302   322  
303   private: 323   private:
HITCBC 304   4699 explicit task(std::coroutine_handle<promise_type> h) 324   5041 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 305   4699 : h_(h) 325   5041 : h_(h)
306   { 326   {
HITCBC 307   4699 } 327   5041 }
308   }; 328   };
309   329  
310   } // namespace capy 330   } // namespace capy
311   } // namespace boost 331   } // namespace boost
312   332  
313   #endif 333   #endif