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