100.00% Lines (95/95) 100.00% Functions (28/28)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
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_QUITTER_HPP 10   #ifndef BOOST_CAPY_QUITTER_HPP
11   #define BOOST_CAPY_QUITTER_HPP 11   #define BOOST_CAPY_QUITTER_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/detail/stop_requested_exception.hpp> 14   #include <boost/capy/detail/stop_requested_exception.hpp>
15   #include <boost/capy/concept/executor.hpp> 15   #include <boost/capy/concept/executor.hpp>
16   #include <boost/capy/concept/io_awaitable.hpp> 16   #include <boost/capy/concept/io_awaitable.hpp>
17   #include <boost/capy/ex/io_awaitable_promise_base.hpp> 17   #include <boost/capy/ex/io_awaitable_promise_base.hpp>
18   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
19   #include <boost/capy/ex/frame_allocator.hpp> 19   #include <boost/capy/ex/frame_allocator.hpp>
20   #include <boost/capy/detail/await_suspend_helper.hpp> 20   #include <boost/capy/detail/await_suspend_helper.hpp>
21   21  
22   #include <exception> 22   #include <exception>
23   #include <optional> 23   #include <optional>
24   #include <type_traits> 24   #include <type_traits>
25   #include <utility> 25   #include <utility>
26   26  
27   /* Stop-aware coroutine task. 27   /* Stop-aware coroutine task.
28   28  
29   quitter<T> is identical to task<T> except that when the stop token 29   quitter<T> is identical to task<T> except that when the stop token
30   is triggered, the coroutine body never sees the cancellation. The 30   is triggered, the coroutine body never sees the cancellation. The
31   promise intercepts it on resume (in transform_awaiter::await_resume) 31   promise intercepts it on resume (in transform_awaiter::await_resume)
32   and throws a sentinel exception that unwinds through RAII destructors 32   and throws a sentinel exception that unwinds through RAII destructors
33   to final_suspend. The parent sees a "stopped" completion. 33   to final_suspend. The parent sees a "stopped" completion.
34   34  
35   See doc/quitter.md for the full design rationale. */ 35   See doc/quitter.md for the full design rationale. */
36   36  
37   namespace boost { 37   namespace boost {
38   namespace capy { 38   namespace capy {
39   39  
40   namespace detail { 40   namespace detail {
41   41  
42   // Reuse the same return-value storage as task<T>. 42   // Reuse the same return-value storage as task<T>.
43   // task_return_base is defined in task.hpp, but quitter needs its own 43   // task_return_base is defined in task.hpp, but quitter needs its own
44   // copy to avoid a header dependency on task.hpp. 44   // copy to avoid a header dependency on task.hpp.
45   template<typename T> 45   template<typename T>
46   struct quitter_return_base 46   struct quitter_return_base
47   { 47   {
48   std::optional<T> result_; 48   std::optional<T> result_;
49   49  
HITCBC 50   11 void return_value(T value) 50   11 void return_value(T value)
51   { 51   {
HITCBC 52   11 result_ = std::move(value); 52   11 result_ = std::move(value);
HITCBC 53   11 } 53   11 }
54   54  
HITCBC 55   5 T&& result() noexcept 55   5 T&& result() noexcept
56   { 56   {
HITCBC 57   5 return std::move(*result_); 57   5 return std::move(*result_);
58   } 58   }
59   }; 59   };
60   60  
61   template<> 61   template<>
62   struct quitter_return_base<void> 62   struct quitter_return_base<void>
63   { 63   {
HITCBC 64   2 void return_void() 64   2 void return_void()
65   { 65   {
HITCBC 66   2 } 66   2 }
67   }; 67   };
68   68  
69   } // namespace detail 69   } // namespace detail
70   70  
71   /** Stop-aware lazy coroutine task satisfying @ref IoRunnable. 71   /** Stop-aware lazy coroutine task satisfying @ref IoRunnable.
72   72  
73   When the stop token is triggered, the next `co_await` inside the 73   When the stop token is triggered, the next `co_await` inside the
74   coroutine short-circuits: the body never sees the result and RAII 74   coroutine short-circuits: the body never sees the result and RAII
75   destructors run normally. The parent observes a "stopped" 75   destructors run normally. The parent observes a "stopped"
76   completion via @ref promise_type::stopped. 76   completion via @ref promise_type::stopped.
77   77  
78   Everything else — frame allocation, environment propagation, 78   Everything else — frame allocation, environment propagation,
79   symmetric transfer, move semantics — is identical to @ref task. 79   symmetric transfer, move semantics — is identical to @ref task.
80   80  
81   @tparam T The result type. Use `quitter<>` for `quitter<void>`. 81   @tparam T The result type. Use `quitter<>` for `quitter<void>`.
82   82  
83   @see task, IoRunnable, IoAwaitable 83   @see task, IoRunnable, IoAwaitable
84   */ 84   */
85   template<typename T = void> 85   template<typename T = void>
86   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE 86   struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
87   quitter 87   quitter
88   { 88   {
89   struct promise_type 89   struct promise_type
90   : io_awaitable_promise_base<promise_type> 90   : io_awaitable_promise_base<promise_type>
91   , detail::quitter_return_base<T> 91   , detail::quitter_return_base<T>
92   { 92   {
93   private: 93   private:
94   friend quitter; 94   friend quitter;
95   95  
96   enum class completion { running, value, exception, stopped }; 96   enum class completion { running, value, exception, stopped };
97   97  
98   union { std::exception_ptr ep_; }; 98   union { std::exception_ptr ep_; };
99   completion state_; 99   completion state_;
100   100  
101   public: 101   public:
HITCBC 102   33 promise_type() noexcept 102   33 promise_type() noexcept
HITCBC 103   33 : state_(completion::running) 103   33 : state_(completion::running)
104   { 104   {
HITCBC 105   33 } 105   33 }
106   106  
HITCBC 107   33 ~promise_type() 107   33 ~promise_type()
108   { 108   {
HITCBC 109   33 if(state_ == completion::exception || 109   33 if(state_ == completion::exception ||
HITCBC 110   29 state_ == completion::stopped) 110   29 state_ == completion::stopped)
HITCBC 111   20 ep_.~exception_ptr(); 111   20 ep_.~exception_ptr();
HITCBC 112   33 } 112   33 }
113   113  
114   /// Return a non-null exception_ptr when the coroutine threw 114   /// Return a non-null exception_ptr when the coroutine threw
115   /// or was stopped. Stopped quitters report the sentinel 115   /// or was stopped. Stopped quitters report the sentinel
116   /// stop_requested_exception so that run_async routes to 116   /// stop_requested_exception so that run_async routes to
117   /// the error handler instead of accessing a non-existent 117   /// the error handler instead of accessing a non-existent
118   /// result. 118   /// result.
HITCBC 119   26 std::exception_ptr exception() const noexcept 119   26 std::exception_ptr exception() const noexcept
120   { 120   {
HITCBC 121   26 if(state_ == completion::exception || 121   26 if(state_ == completion::exception ||
HITCBC 122   20 state_ == completion::stopped) 122   20 state_ == completion::stopped)
HITCBC 123   20 return ep_; 123   20 return ep_;
HITCBC 124   6 return {}; 124   6 return {};
125   } 125   }
126   126  
127   /// True when the coroutine was stopped via the stop token. 127   /// True when the coroutine was stopped via the stop token.
HITCBC 128   12 bool stopped() const noexcept 128   12 bool stopped() const noexcept
129   { 129   {
HITCBC 130   12 return state_ == completion::stopped; 130   12 return state_ == completion::stopped;
131   } 131   }
132   132  
HITCBC 133   33 quitter get_return_object() 133   33 quitter get_return_object()
134   { 134   {
135   return quitter{ 135   return quitter{
HITCBC 136   33 std::coroutine_handle<promise_type>::from_promise(*this)}; 136   33 std::coroutine_handle<promise_type>::from_promise(*this)};
137   } 137   }
138   138  
HITCBC 139   33 auto initial_suspend() noexcept 139   33 auto initial_suspend() noexcept
140   { 140   {
141   struct awaiter 141   struct awaiter
142   { 142   {
143   promise_type* p_; 143   promise_type* p_;
144   144  
HITCBC 145   33 bool await_ready() const noexcept 145   33 bool await_ready() const noexcept
146   { 146   {
HITCBC 147   33 return false; 147   33 return false;
148   } 148   }
149   149  
HITCBC 150   33 void await_suspend(std::coroutine_handle<>) const noexcept 150   33 void await_suspend(std::coroutine_handle<>) const noexcept
151   { 151   {
HITCBC 152   33 } 152   33 }
153   153  
154   // Potentially-throwing: checks the stop token before 154   // Potentially-throwing: checks the stop token before
155   // the coroutine body executes its first statement. 155   // the coroutine body executes its first statement.
HITCBC 156   33 void await_resume() const 156   33 void await_resume() const
157   { 157   {
HITCBC 158   33 set_current_frame_allocator( 158   33 set_current_frame_allocator(
HITCBC 159   33 p_->environment()->frame_allocator); 159   33 p_->environment()->frame_allocator);
HITCBC 160   33 if(p_->environment()->stop_token.stop_requested()) 160   33 if(p_->environment()->stop_token.stop_requested())
HITCBC 161   3 throw detail::stop_requested_exception{}; 161   3 throw detail::stop_requested_exception{};
HITCBC 162   30 } 162   30 }
163   }; 163   };
HITCBC 164   33 return awaiter{this}; 164   33 return awaiter{this};
165   } 165   }
166   166  
HITCBC 167   33 auto final_suspend() noexcept 167   33 auto final_suspend() noexcept
168   { 168   {
169   struct awaiter 169   struct awaiter
170   { 170   {
171   promise_type* p_; 171   promise_type* p_;
172   172  
HITCBC 173   33 bool await_ready() const noexcept 173   33 bool await_ready() const noexcept
174   { 174   {
HITCBC 175   33 return false; 175   33 return false;
176   } 176   }
177   177  
HITCBC 178   33 std::coroutine_handle<> await_suspend( 178   33 std::coroutine_handle<> await_suspend(
179   std::coroutine_handle<>) const noexcept 179   std::coroutine_handle<>) const noexcept
180   { 180   {
HITCBC 181   33 return p_->continuation(); 181   33 return p_->continuation();
182   } 182   }
183   183  
184   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 184   void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed
185   }; 185   };
HITCBC 186   33 return awaiter{this}; 186   33 return awaiter{this};
187   } 187   }
188   188  
HITCBC 189   20 void unhandled_exception() 189   20 void unhandled_exception()
190   { 190   {
191   try 191   try
192   { 192   {
HITCBC 193   20 throw; 193   20 throw;
194   } 194   }
HITCBC 195   20 catch(detail::stop_requested_exception const&) 195   20 catch(detail::stop_requested_exception const&)
196   { 196   {
197   // Store the exception_ptr so that run_async's 197   // Store the exception_ptr so that run_async's
198   // invoke_impl routes to the error handler 198   // invoke_impl routes to the error handler
199   // instead of accessing a non-existent result. 199   // instead of accessing a non-existent result.
HITCBC 200   16 new (&ep_) std::exception_ptr( 200   16 new (&ep_) std::exception_ptr(
201   std::current_exception()); 201   std::current_exception());
HITCBC 202   16 state_ = completion::stopped; 202   16 state_ = completion::stopped;
203   } 203   }
HITCBC 204   4 catch(...) 204   4 catch(...)
205   { 205   {
HITCBC 206   4 new (&ep_) std::exception_ptr( 206   4 new (&ep_) std::exception_ptr(
207   std::current_exception()); 207   std::current_exception());
HITCBC 208   4 state_ = completion::exception; 208   4 state_ = completion::exception;
209   } 209   }
HITCBC 210   20 } 210   20 }
211   211  
212   //------------------------------------------------------ 212   //------------------------------------------------------
213   // transform_awaitable — the key difference from task<T> 213   // transform_awaitable — the key difference from task<T>
214   //------------------------------------------------------ 214   //------------------------------------------------------
215   215  
216   template<class Awaitable> 216   template<class Awaitable>
217   struct transform_awaiter 217   struct transform_awaiter
218   { 218   {
219   std::decay_t<Awaitable> a_; 219   std::decay_t<Awaitable> a_;
220   promise_type* p_; 220   promise_type* p_;
221   221  
HITCBC 222   21 bool await_ready() noexcept 222   21 bool await_ready() noexcept
223   { 223   {
HITCBC 224   21 return a_.await_ready(); 224   21 return a_.await_ready();
225   } 225   }
226   226  
227   // Check the stop token BEFORE the coroutine body 227   // Check the stop token BEFORE the coroutine body
228   // sees the result of the I/O operation. 228   // sees the result of the I/O operation.
HITCBC 229   21 decltype(auto) await_resume() 229   21 decltype(auto) await_resume()
230   { 230   {
HITCBC 231   21 set_current_frame_allocator( 231   21 set_current_frame_allocator(
HITCBC 232   21 p_->environment()->frame_allocator); 232   21 p_->environment()->frame_allocator);
HITCBC 233   21 if(p_->environment()->stop_token.stop_requested()) 233   21 if(p_->environment()->stop_token.stop_requested())
HITCBC 234   13 throw detail::stop_requested_exception{}; 234   13 throw detail::stop_requested_exception{};
HITCBC 235   8 return a_.await_resume(); 235   8 return a_.await_resume();
236   } 236   }
237   237  
238   template<class Promise> 238   template<class Promise>
HITCBC 239   19 auto await_suspend( 239   19 auto await_suspend(
240   std::coroutine_handle<Promise> h) noexcept 240   std::coroutine_handle<Promise> h) noexcept
241   { 241   {
242   using R = decltype( 242   using R = decltype(
243   a_.await_suspend(h, p_->environment())); 243   a_.await_suspend(h, p_->environment()));
244   if constexpr (std::is_same_v< 244   if constexpr (std::is_same_v<
245   R, std::coroutine_handle<>>) 245   R, std::coroutine_handle<>>)
HITCBC 246   18 return detail::symmetric_transfer( 246   18 return detail::symmetric_transfer(
HITCBC 247   36 a_.await_suspend(h, p_->environment())); 247   36 a_.await_suspend(h, p_->environment()));
248   else 248   else
HITCBC 249   1 return a_.await_suspend( 249   1 return a_.await_suspend(
HITCBC 250   2 h, p_->environment()); 250   2 h, p_->environment());
251   } 251   }
252   }; 252   };
253   253  
254   template<class Awaitable> 254   template<class Awaitable>
HITCBC 255   21 auto transform_awaitable(Awaitable&& a) 255   21 auto transform_awaitable(Awaitable&& a)
256   { 256   {
257   using A = std::decay_t<Awaitable>; 257   using A = std::decay_t<Awaitable>;
258   if constexpr (IoAwaitable<A>) 258   if constexpr (IoAwaitable<A>)
259   { 259   {
260   return transform_awaiter<Awaitable>{ 260   return transform_awaiter<Awaitable>{
HITCBC 261   38 std::forward<Awaitable>(a), this}; 261   38 std::forward<Awaitable>(a), this};
262   } 262   }
263   else 263   else
264   { 264   {
265   static_assert(sizeof(A) == 0, 265   static_assert(sizeof(A) == 0,
266   "requires IoAwaitable"); 266   "requires IoAwaitable");
267   } 267   }
HITCBC 268   17 } 268   17 }
269   }; 269   };
270   270  
271   std::coroutine_handle<promise_type> h_; 271   std::coroutine_handle<promise_type> h_;
272   272  
273   /// Destroy the quitter and its coroutine frame if owned. 273   /// Destroy the quitter and its coroutine frame if owned.
HITCBC 274   82 ~quitter() 274   82 ~quitter()
275   { 275   {
HITCBC 276   82 if(h_) 276   82 if(h_)
HITCBC 277   15 h_.destroy(); 277   15 h_.destroy();
HITCBC 278   82 } 278   82 }
279   279  
280   /// Return false; quitters are never immediately ready. 280   /// Return false; quitters are never immediately ready.
HITCBC 281   15 bool await_ready() const noexcept 281   15 bool await_ready() const noexcept
282   { 282   {
HITCBC 283   15 return false; 283   15 return false;
284   } 284   }
285   285  
286   /** Return the result, rethrow exception, or propagate stop. 286   /** Return the result, rethrow exception, or propagate stop.
287   287  
288   When stopped, throws stop_requested_exception so that a 288   When stopped, throws stop_requested_exception so that a
289   parent quitter also stops. A parent task<T> will see this 289   parent quitter also stops. A parent task<T> will see this
290   as an unhandled exception — by design. 290   as an unhandled exception — by design.
291   */ 291   */
HITCBC 292   12 auto await_resume() 292   12 auto await_resume()
293   { 293   {
HITCBC 294   12 if(h_.promise().stopped()) 294   12 if(h_.promise().stopped())
HITCBC 295   6 throw detail::stop_requested_exception{}; 295   6 throw detail::stop_requested_exception{};
HITCBC 296   6 if(h_.promise().state_ == promise_type::completion::exception) 296   6 if(h_.promise().state_ == promise_type::completion::exception)
HITCBC 297   1 std::rethrow_exception(h_.promise().ep_); 297   1 std::rethrow_exception(h_.promise().ep_);
298   if constexpr (! std::is_void_v<T>) 298   if constexpr (! std::is_void_v<T>)
HITCBC 299   4 return std::move(*h_.promise().result_); 299   4 return std::move(*h_.promise().result_);
300   else 300   else
HITCBC 301   1 return; 301   1 return;
302   } 302   }
303   303  
304   /// Start execution with the caller's context. 304   /// Start execution with the caller's context.
HITCBC 305   15 std::coroutine_handle<> await_suspend( 305   15 std::coroutine_handle<> await_suspend(
306   std::coroutine_handle<> cont, 306   std::coroutine_handle<> cont,
307   io_env const* env) 307   io_env const* env)
308   { 308   {
HITCBC 309   15 h_.promise().set_continuation(cont); 309   15 h_.promise().set_continuation(cont);
HITCBC 310   15 h_.promise().set_environment(env); 310   15 h_.promise().set_environment(env);
HITCBC 311   15 return h_; 311   15 return h_;
312   } 312   }
313   313  
314   /** Return the coroutine handle. 314   /** Return the coroutine handle.
315   315  
316   @note Do not call `destroy()` on the returned handle while 316   @note Do not call `destroy()` on the returned handle while
317   the quitter is being awaited. The quitter's lifetime is 317   the quitter is being awaited. The quitter's lifetime is
318   normally managed by `run_async`, `run`, or the awaiting 318   normally managed by `run_async`, `run`, or the awaiting
319   parent; manually destroying a suspended quitter that another 319   parent; manually destroying a suspended quitter that another
320   coroutine is awaiting produces undefined behavior. For 320   coroutine is awaiting produces undefined behavior. For
321   cooperative cancellation, use `std::stop_token`. 321   cooperative cancellation, use `std::stop_token`.
322   322  
323   @return The coroutine handle. 323   @return The coroutine handle.
324   */ 324   */
HITCBC 325   20 std::coroutine_handle<promise_type> handle() const noexcept 325   20 std::coroutine_handle<promise_type> handle() const noexcept
326   { 326   {
HITCBC 327   20 return h_; 327   20 return h_;
328   } 328   }
329   329  
330   /** Release ownership of the coroutine frame. 330   /** Release ownership of the coroutine frame.
331   331  
332   @note If the caller intends to call `destroy()` on the 332   @note If the caller intends to call `destroy()` on the
333   released handle, it must do so only when the quitter has not 333   released handle, it must do so only when the quitter has not
334   started or has fully completed. Destroying a suspended 334   started or has fully completed. Destroying a suspended
335   quitter that is being awaited produces undefined behavior. 335   quitter that is being awaited produces undefined behavior.
336   */ 336   */
HITCBC 337   18 void release() noexcept 337   18 void release() noexcept
338   { 338   {
HITCBC 339   18 h_ = nullptr; 339   18 h_ = nullptr;
HITCBC 340   18 } 340   18 }
341   341  
342   quitter(quitter const&) = delete; 342   quitter(quitter const&) = delete;
343   quitter& operator=(quitter const&) = delete; 343   quitter& operator=(quitter const&) = delete;
344   344  
345   /// Construct by moving, transferring ownership. 345   /// Construct by moving, transferring ownership.
HITCBC 346   49 quitter(quitter&& other) noexcept 346   49 quitter(quitter&& other) noexcept
HITCBC 347   49 : h_(std::exchange(other.h_, nullptr)) 347   49 : h_(std::exchange(other.h_, nullptr))
348   { 348   {
HITCBC 349   49 } 349   49 }
350   350  
351   /// Assign by moving, transferring ownership. 351   /// Assign by moving, transferring ownership.
352   quitter& operator=(quitter&& other) noexcept 352   quitter& operator=(quitter&& other) noexcept
353   { 353   {
354   if(this != &other) 354   if(this != &other)
355   { 355   {
356   if(h_) 356   if(h_)
357   h_.destroy(); 357   h_.destroy();
358   h_ = std::exchange(other.h_, nullptr); 358   h_ = std::exchange(other.h_, nullptr);
359   } 359   }
360   return *this; 360   return *this;
361   } 361   }
362   362  
363   private: 363   private:
HITCBC 364   33 explicit quitter(std::coroutine_handle<promise_type> h) 364   33 explicit quitter(std::coroutine_handle<promise_type> h)
HITCBC 365   33 : h_(h) 365   33 : h_(h)
366   { 366   {
HITCBC 367   33 } 367   33 }
368   }; 368   };
369   369  
370   } // namespace capy 370   } // namespace capy
371   } // namespace boost 371   } // namespace boost
372   372  
373   #endif 373   #endif