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