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 <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   1317 void return_value(T value) 38   1303 void return_value(T value)
39   { 39   {
HITCBC 40   1317 result_ = std::move(value); 40   1303 result_ = std::move(value);
HITCBC 41   1317 } 41   1303 }
42   42  
HITCBC 43   160 T&& result() noexcept 43   154 T&& result() noexcept
44   { 44   {
HITCBC 45   160 return std::move(*result_); 45   154 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   2012 void return_void() 52   2009 void return_void()
53   { 53   {
HITCBC 54   2012 } 54   2009 }
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   struct promise_type 101   struct promise_type
102   : io_awaitable_promise_base<promise_type> 102   : io_awaitable_promise_base<promise_type>
103   , detail::task_return_base<T> 103   , detail::task_return_base<T>
104   { 104   {
105   private: 105   private:
106   friend task; 106   friend task;
107   union { std::exception_ptr ep_; }; 107   union { std::exception_ptr ep_; };
108   bool has_ep_; 108   bool has_ep_;
109   109  
110   public: 110   public:
HITCBC 111   5097 promise_type() noexcept 111   5075 promise_type() noexcept
HITCBC 112   5097 : has_ep_(false) 112   5075 : has_ep_(false)
113   { 113   {
HITCBC 114   5097 } 114   5075 }
115   115  
HITCBC 116   5097 ~promise_type() 116   5075 ~promise_type()
117   { 117   {
HITCBC 118   5097 if(has_ep_) 118   5075 if(has_ep_)
HITCBC 119   1612 ep_.~exception_ptr(); 119   1607 ep_.~exception_ptr();
HITCBC 120   5097 } 120   5075 }
121   121  
HITCBC 122   4207 std::exception_ptr exception() const noexcept 122   4195 std::exception_ptr exception() const noexcept
123   { 123   {
HITCBC 124   4207 if(has_ep_) 124   4195 if(has_ep_)
HITCBC 125   2108 return ep_; 125   2102 return ep_;
HITCBC 126   2099 return {}; 126   2093 return {};
127   } 127   }
128   128  
HITCBC 129   5097 task get_return_object() 129   5075 task get_return_object()
130   { 130   {
HITCBC 131   5097 return task{std::coroutine_handle<promise_type>::from_promise(*this)}; 131   5075 return task{std::coroutine_handle<promise_type>::from_promise(*this)};
132   } 132   }
133   133  
HITCBC 134   5097 auto initial_suspend() noexcept 134   5075 auto initial_suspend() noexcept
135   { 135   {
136   struct awaiter 136   struct awaiter
137   { 137   {
138   promise_type* p_; 138   promise_type* p_;
139   139  
HITCBC 140   5097 bool await_ready() const noexcept 140   5075 bool await_ready() const noexcept
141   { 141   {
HITCBC 142   5097 return false; 142   5075 return false;
143   } 143   }
144   144  
HITCBC 145   5097 void await_suspend(std::coroutine_handle<>) const noexcept 145   5075 void await_suspend(std::coroutine_handle<>) const noexcept
146   { 146   {
HITCBC 147   5097 } 147   5075 }
148   148  
HITCBC 149   5094 void await_resume() const noexcept 149   5072 void await_resume() const noexcept
150   { 150   {
151   // Restore TLS when body starts executing 151   // Restore TLS when body starts executing
HITCBC 152   5094 set_current_frame_allocator(p_->environment()->frame_allocator); 152   5072 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 153   5094 } 153   5072 }
154   }; 154   };
HITCBC 155   5097 return awaiter{this}; 155   5075 return awaiter{this};
156   } 156   }
157   157  
HITCBC 158   4941 auto final_suspend() noexcept 158   4919 auto final_suspend() noexcept
159   { 159   {
160   struct awaiter 160   struct awaiter
161   { 161   {
162   promise_type* p_; 162   promise_type* p_;
163   163  
HITCBC 164   4941 bool await_ready() const noexcept 164   4919 bool await_ready() const noexcept
165   { 165   {
HITCBC 166   4941 return false; 166   4919 return false;
167   } 167   }
168   168  
HITCBC 169   4941 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept 169   4919 std::coroutine_handle<> await_suspend(std::coroutine_handle<>) const noexcept
170   { 170   {
HITCBC 171   4941 return p_->continuation(); 171   4919 return p_->continuation();
172   } 172   }
173   173  
MISUIC 174 - void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 174 + void await_resume() const noexcept
  175 + {
MISUNC   176 + }
175   }; 177   };
HITCBC 176   4941 return awaiter{this}; 178   4919 return awaiter{this};
177   } 179   }
178   180  
HITCBC 179   1612 void unhandled_exception() noexcept 181   1607 void unhandled_exception() noexcept
180   { 182   {
HITCBC 181   1612 new (&ep_) std::exception_ptr(std::current_exception()); 183   1607 new (&ep_) std::exception_ptr(std::current_exception());
HITCBC 182   1612 has_ep_ = true; 184   1607 has_ep_ = true;
HITCBC 183   1612 } 185   1607 }
184   186  
185   template<class Awaitable> 187   template<class Awaitable>
186   struct transform_awaiter 188   struct transform_awaiter
187   { 189   {
188   std::decay_t<Awaitable> a_; 190   std::decay_t<Awaitable> a_;
189   promise_type* p_; 191   promise_type* p_;
190   192  
HITCBC 191   9253 bool await_ready() noexcept 193   9238 bool await_ready() noexcept
192   { 194   {
HITCBC 193   9253 return a_.await_ready(); 195   9238 return a_.await_ready();
194   } 196   }
195   197  
HITCBC 196   9100 decltype(auto) await_resume() 198   9085 decltype(auto) await_resume()
197   { 199   {
198   // Restore TLS before body resumes 200   // Restore TLS before body resumes
HITCBC 199   9100 set_current_frame_allocator(p_->environment()->frame_allocator); 201   9085 set_current_frame_allocator(p_->environment()->frame_allocator);
HITCBC 200   9100 return a_.await_resume(); 202   9085 return a_.await_resume();
201   } 203   }
202   204  
203   template<class Promise> 205   template<class Promise>
HITCBC 204   2544 auto await_suspend(std::coroutine_handle<Promise> h) noexcept 206   2530 auto await_suspend(std::coroutine_handle<Promise> h) noexcept
205   { 207   {
206   using R = decltype(a_.await_suspend(h, p_->environment())); 208   using R = decltype(a_.await_suspend(h, p_->environment()));
207   if constexpr (std::is_same_v<R, std::coroutine_handle<>>) 209   if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
HITCBC 208   2543 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment())); 210   2530 return detail::symmetric_transfer(a_.await_suspend(h, p_->environment()));
209   else 211   else
MISLBC 210   1 return a_.await_suspend(h, p_->environment()); 212   return a_.await_suspend(h, p_->environment());
211   } 213   }
212   }; 214   };
213   215  
214   template<class Awaitable> 216   template<class Awaitable>
HITCBC 215   9253 auto transform_awaitable(Awaitable&& a) 217   9238 auto transform_awaitable(Awaitable&& a)
216   { 218   {
217   using A = std::decay_t<Awaitable>; 219   using A = std::decay_t<Awaitable>;
218   if constexpr (IoAwaitable<A>) 220   if constexpr (IoAwaitable<A>)
219   { 221   {
220   return transform_awaiter<Awaitable>{ 222   return transform_awaiter<Awaitable>{
HITCBC 221   11450 std::forward<Awaitable>(a), this}; 223   11427 std::forward<Awaitable>(a), this};
222   } 224   }
223   else 225   else
224   { 226   {
225   static_assert(sizeof(A) == 0, "requires IoAwaitable"); 227   static_assert(sizeof(A) == 0, "requires IoAwaitable");
226   } 228   }
HITCBC 227   2197 } 229   2189 }
228   }; 230   };
229   231  
230   std::coroutine_handle<promise_type> h_; 232   std::coroutine_handle<promise_type> h_;
231   233  
232   /// Destroy the task and its coroutine frame if owned. 234   /// Destroy the task and its coroutine frame if owned.
HITCBC 233   10553 ~task() 235   10486 ~task()
234   { 236   {
HITCBC 235   10553 if(h_) 237   10486 if(h_)
HITCBC 236   1731 h_.destroy(); 238   1721 h_.destroy();
HITCBC 237   10553 } 239   10486 }
238   240  
239   /// Return false; tasks are never immediately ready. 241   /// Return false; tasks are never immediately ready.
HITCBC 240   1593 bool await_ready() const noexcept 242   1583 bool await_ready() const noexcept
241   { 243   {
HITCBC 242   1593 return false; 244   1583 return false;
243   } 245   }
244   246  
245   /// Return the result or rethrow any stored exception. 247   /// Return the result or rethrow any stored exception.
HITCBC 246   1728 auto await_resume() 248   1718 auto await_resume()
247   { 249   {
HITCBC 248   1728 if(h_.promise().has_ep_) 250   1718 if(h_.promise().has_ep_)
HITCBC 249   557 std::rethrow_exception(h_.promise().ep_); 251   555 std::rethrow_exception(h_.promise().ep_);
250   if constexpr (! std::is_void_v<T>) 252   if constexpr (! std::is_void_v<T>)
HITCBC 251   1155 return std::move(*h_.promise().result_); 253   1147 return std::move(*h_.promise().result_);
252   else 254   else
HITCBC 253   16 return; 255   16 return;
254   } 256   }
255   257  
256   /// Start execution with the caller's context. 258   /// Start execution with the caller's context.
HITCBC 257   1706 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env) 259   1696 std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
258   { 260   {
HITCBC 259   1706 h_.promise().set_continuation(cont); 261   1696 h_.promise().set_continuation(cont);
HITCBC 260   1706 h_.promise().set_environment(env); 262   1696 h_.promise().set_environment(env);
HITCBC 261   1706 return h_; 263   1696 return h_;
262   } 264   }
263   265  
264   /** Return the coroutine handle. 266   /** Return the coroutine handle.
265   267  
266   @note Do not call `destroy()` on the returned handle while the 268   @note Do not call `destroy()` on the returned handle while the
267   task is being awaited. The task's lifetime is normally managed 269   task is being awaited. The task's lifetime is normally managed
268   by `run_async`, `run`, or the awaiting parent; manually 270   by `run_async`, `run`, or the awaiting parent; manually
269   destroying a suspended task that another coroutine is awaiting 271   destroying a suspended task that another coroutine is awaiting
270   produces undefined behavior. For cooperative cancellation, use 272   produces undefined behavior. For cooperative cancellation, use
271   `std::stop_token`. 273   `std::stop_token`.
272   274  
273   @return The coroutine handle. 275   @return The coroutine handle.
274   */ 276   */
HITCBC 275   3391 std::coroutine_handle<promise_type> handle() const noexcept 277   3379 std::coroutine_handle<promise_type> handle() const noexcept
276   { 278   {
HITCBC 277   3391 return h_; 279   3379 return h_;
278   } 280   }
279   281  
280   /** Release ownership of the coroutine frame. 282   /** Release ownership of the coroutine frame.
281   283  
282   After calling this, destroying the task does not destroy the 284   After calling this, destroying the task does not destroy the
283   coroutine frame. The caller becomes responsible for the frame's 285   coroutine frame. The caller becomes responsible for the frame's
284   lifetime. 286   lifetime.
285   287  
286   @note If the caller intends to call `destroy()` on the 288   @note If the caller intends to call `destroy()` on the
287   released handle, it must do so only when the task has not 289   released handle, it must do so only when the task has not
288   started or has fully completed. Destroying a suspended task 290   started or has fully completed. Destroying a suspended task
289   that is being awaited produces undefined behavior. 291   that is being awaited produces undefined behavior.
290   292  
291   @par Postconditions 293   @par Postconditions
292   `handle()` returns the original handle, but the task no longer 294   `handle()` returns the original handle, but the task no longer
293   owns it. 295   owns it.
294   */ 296   */
HITCBC 295   3366 void release() noexcept 297   3354 void release() noexcept
296   { 298   {
HITCBC 297   3366 h_ = nullptr; 299   3354 h_ = nullptr;
HITCBC 298   3366 } 300   3354 }
299   301  
300   task(task const&) = delete; 302   task(task const&) = delete;
301   task& operator=(task const&) = delete; 303   task& operator=(task const&) = delete;
302   304  
303   /// Construct by moving, transferring ownership. 305   /// Construct by moving, transferring ownership.
HITCBC 304   5456 task(task&& other) noexcept 306   5411 task(task&& other) noexcept
HITCBC 305   5456 : h_(std::exchange(other.h_, nullptr)) 307   5411 : h_(std::exchange(other.h_, nullptr))
306   { 308   {
HITCBC 307   5456 } 309   5411 }
308   310  
309   /// Assign by moving, transferring ownership. 311   /// Assign by moving, transferring ownership.
310   task& operator=(task&& other) noexcept 312   task& operator=(task&& other) noexcept
311   { 313   {
312   if(this != &other) 314   if(this != &other)
313   { 315   {
314   if(h_) 316   if(h_)
315   h_.destroy(); 317   h_.destroy();
316   h_ = std::exchange(other.h_, nullptr); 318   h_ = std::exchange(other.h_, nullptr);
317   } 319   }
318   return *this; 320   return *this;
319   } 321   }
320   322  
321   private: 323   private:
HITCBC 322   5097 explicit task(std::coroutine_handle<promise_type> h) 324   5075 explicit task(std::coroutine_handle<promise_type> h)
HITCBC 323   5097 : h_(h) 325   5075 : h_(h)
324   { 326   {
HITCBC 325   5097 } 327   5075 }
326   }; 328   };
327   329  
328   } // namespace capy 330   } // namespace capy
329   } // namespace boost 331   } // namespace boost
330   332  
331   #endif 333   #endif