93.81% Lines (91/97) 96.55% Functions (28/29)
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   9 void return_value(T value)
51   { 51   {
HITCBC 52   11 result_ = std::move(value); 52   9 result_ = std::move(value);
HITCBC 53   11 } 53   9 }
54   54  
HITCBC 55   5 T&& result() noexcept 55   3 T&& result() noexcept
56   { 56   {
HITCBC 57   5 return std::move(*result_); 57   3 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   1 void return_void()
65   { 65   {
HITCBC 66   2 } 66   1 }
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   28 promise_type() noexcept
HITCBC 103   33 : state_(completion::running) 103   28 : state_(completion::running)
104   { 104   {
HITCBC 105   33 } 105   28 }
106   106  
HITCBC 107   33 ~promise_type() 107   28 ~promise_type()
108   { 108   {
HITCBC 109   33 if(state_ == completion::exception || 109   28 if(state_ == completion::exception ||
HITCBC 110   29 state_ == completion::stopped) 110   26 state_ == completion::stopped)
HITCBC 111   20 ep_.~exception_ptr(); 111   18 ep_.~exception_ptr();
HITCBC 112   33 } 112   28 }
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   22 std::exception_ptr exception() const noexcept
120   { 120   {
HITCBC 121   26 if(state_ == completion::exception || 121   22 if(state_ == completion::exception ||
HITCBC 122   20 state_ == completion::stopped) 122   18 state_ == completion::stopped)
HITCBC 123   20 return ep_; 123   18 return ep_;
HITCBC 124   6 return {}; 124   4 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   10 bool stopped() const noexcept
129   { 129   {
HITCBC 130   12 return state_ == completion::stopped; 130   10 return state_ == completion::stopped;
131   } 131   }
132   132  
HITCBC 133   33 quitter get_return_object() 133   28 quitter get_return_object()
134   { 134   {
135   return quitter{ 135   return quitter{
HITCBC 136   33 std::coroutine_handle<promise_type>::from_promise(*this)}; 136   28 std::coroutine_handle<promise_type>::from_promise(*this)};
137   } 137   }
138   138  
HITCBC 139   33 auto initial_suspend() noexcept 139   28 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   28 bool await_ready() const noexcept
146   { 146   {
HITCBC 147   33 return false; 147   28 return false;
148   } 148   }
149   149  
HITCBC 150   33 void await_suspend(std::coroutine_handle<>) const noexcept 150   28 void await_suspend(std::coroutine_handle<>) const noexcept
151   { 151   {
HITCBC 152   33 } 152   28 }
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   28 void await_resume() const
157   { 157   {
HITCBC 158   33 set_current_frame_allocator( 158   28 set_current_frame_allocator(
HITCBC 159   33 p_->environment()->frame_allocator); 159   28 p_->environment()->frame_allocator);
HITCBC 160   33 if(p_->environment()->stop_token.stop_requested()) 160   28 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   25 }
163   }; 163   };
HITCBC 164   33 return awaiter{this}; 164   28 return awaiter{this};
165   } 165   }
166   166  
HITCBC 167   33 auto final_suspend() noexcept 167   28 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   28 bool await_ready() const noexcept
174   { 174   {
HITCBC 175   33 return false; 175   28 return false;
176   } 176   }
177   177  
HITCBC 178   33 std::coroutine_handle<> await_suspend( 178   28 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   28 return p_->continuation();
182   } 182   }
183   183  
MISUIC 184 - void await_resume() const noexcept {} // LCOV_EXCL_LINE final_suspend awaiter, never resumed 184 + void await_resume() const noexcept
  185 + {
MISUNC   186 + }
185   }; 187   };
HITCBC 186   33 return awaiter{this}; 188   28 return awaiter{this};
187   } 189   }
188   190  
HITCBC 189   20 void unhandled_exception() 191   18 void unhandled_exception()
190   { 192   {
191   try 193   try
192   { 194   {
HITCBC 193   20 throw; 195   18 throw;
194   } 196   }
HITCBC 195   20 catch(detail::stop_requested_exception const&) 197   18 catch(detail::stop_requested_exception const&)
196   { 198   {
197   // Store the exception_ptr so that run_async's 199   // Store the exception_ptr so that run_async's
198   // invoke_impl routes to the error handler 200   // invoke_impl routes to the error handler
199   // instead of accessing a non-existent result. 201   // instead of accessing a non-existent result.
HITCBC 200   16 new (&ep_) std::exception_ptr( 202   16 new (&ep_) std::exception_ptr(
201   std::current_exception()); 203   std::current_exception());
HITCBC 202   16 state_ = completion::stopped; 204   16 state_ = completion::stopped;
203   } 205   }
HITCBC 204   4 catch(...) 206   2 catch(...)
205   { 207   {
HITCBC 206   4 new (&ep_) std::exception_ptr( 208   2 new (&ep_) std::exception_ptr(
207   std::current_exception()); 209   std::current_exception());
HITCBC 208   4 state_ = completion::exception; 210   2 state_ = completion::exception;
209   } 211   }
HITCBC 210   20 } 212   18 }
211   213  
212   //------------------------------------------------------ 214   //------------------------------------------------------
213   // transform_awaitable — the key difference from task<T> 215   // transform_awaitable — the key difference from task<T>
214   //------------------------------------------------------ 216   //------------------------------------------------------
215   217  
216   template<class Awaitable> 218   template<class Awaitable>
217   struct transform_awaiter 219   struct transform_awaiter
218   { 220   {
219   std::decay_t<Awaitable> a_; 221   std::decay_t<Awaitable> a_;
220   promise_type* p_; 222   promise_type* p_;
221   223  
HITCBC 222   21 bool await_ready() noexcept 224   18 bool await_ready() noexcept
223   { 225   {
HITCBC 224   21 return a_.await_ready(); 226   18 return a_.await_ready();
225   } 227   }
226   228  
227   // Check the stop token BEFORE the coroutine body 229   // Check the stop token BEFORE the coroutine body
228   // sees the result of the I/O operation. 230   // sees the result of the I/O operation.
HITCBC 229   21 decltype(auto) await_resume() 231   18 decltype(auto) await_resume()
230   { 232   {
HITCBC 231   21 set_current_frame_allocator( 233   18 set_current_frame_allocator(
HITCBC 232   21 p_->environment()->frame_allocator); 234   18 p_->environment()->frame_allocator);
HITCBC 233   21 if(p_->environment()->stop_token.stop_requested()) 235   18 if(p_->environment()->stop_token.stop_requested())
HITCBC 234   13 throw detail::stop_requested_exception{}; 236   13 throw detail::stop_requested_exception{};
HITCBC 235   8 return a_.await_resume(); 237   5 return a_.await_resume();
236   } 238   }
237   239  
238   template<class Promise> 240   template<class Promise>
HITCBC 239   19 auto await_suspend( 241   16 auto await_suspend(
240   std::coroutine_handle<Promise> h) noexcept 242   std::coroutine_handle<Promise> h) noexcept
241   { 243   {
242   using R = decltype( 244   using R = decltype(
243   a_.await_suspend(h, p_->environment())); 245   a_.await_suspend(h, p_->environment()));
244   if constexpr (std::is_same_v< 246   if constexpr (std::is_same_v<
245   R, std::coroutine_handle<>>) 247   R, std::coroutine_handle<>>)
HITCBC 246   18 return detail::symmetric_transfer( 248   16 return detail::symmetric_transfer(
HITCBC 247   36 a_.await_suspend(h, p_->environment())); 249   32 a_.await_suspend(h, p_->environment()));
248   else 250   else
MISLBC 249   1 return a_.await_suspend( 251   return a_.await_suspend(
MISLBC 250   2 h, p_->environment()); 252   h, p_->environment());
251   } 253   }
252   }; 254   };
253   255  
254   template<class Awaitable> 256   template<class Awaitable>
HITCBC 255   21 auto transform_awaitable(Awaitable&& a) 257   18 auto transform_awaitable(Awaitable&& a)
256   { 258   {
257   using A = std::decay_t<Awaitable>; 259   using A = std::decay_t<Awaitable>;
258   if constexpr (IoAwaitable<A>) 260   if constexpr (IoAwaitable<A>)
259   { 261   {
260   return transform_awaiter<Awaitable>{ 262   return transform_awaiter<Awaitable>{
HITCBC 261   38 std::forward<Awaitable>(a), this}; 263   33 std::forward<Awaitable>(a), this};
262   } 264   }
263   else 265   else
264   { 266   {
265   static_assert(sizeof(A) == 0, 267   static_assert(sizeof(A) == 0,
266   "requires IoAwaitable"); 268   "requires IoAwaitable");
267   } 269   }
HITCBC 268   17 } 270   15 }
269   }; 271   };
270   272  
271   std::coroutine_handle<promise_type> h_; 273   std::coroutine_handle<promise_type> h_;
272   274  
273   /// Destroy the quitter and its coroutine frame if owned. 275   /// Destroy the quitter and its coroutine frame if owned.
HITCBC 274   82 ~quitter() 276   72 ~quitter()
275   { 277   {
HITCBC 276   82 if(h_) 278   72 if(h_)
HITCBC 277   15 h_.destroy(); 279   13 h_.destroy();
HITCBC 278   82 } 280   72 }
279   281  
280   /// Return false; quitters are never immediately ready. 282   /// Return false; quitters are never immediately ready.
HITCBC 281   15 bool await_ready() const noexcept 283   13 bool await_ready() const noexcept
282   { 284   {
HITCBC 283   15 return false; 285   13 return false;
284   } 286   }
285   287  
286   /** Return the result, rethrow exception, or propagate stop. 288   /** Return the result, rethrow exception, or propagate stop.
287   289  
288   When stopped, throws stop_requested_exception so that a 290   When stopped, throws stop_requested_exception so that a
289   parent quitter also stops. A parent task<T> will see this 291   parent quitter also stops. A parent task<T> will see this
290   as an unhandled exception — by design. 292   as an unhandled exception — by design.
291   */ 293   */
HITCBC 292   12 auto await_resume() 294   10 auto await_resume()
293   { 295   {
HITCBC 294   12 if(h_.promise().stopped()) 296   10 if(h_.promise().stopped())
HITCBC 295   6 throw detail::stop_requested_exception{}; 297   6 throw detail::stop_requested_exception{};
HITCBC 296   6 if(h_.promise().state_ == promise_type::completion::exception) 298   4 if(h_.promise().state_ == promise_type::completion::exception)
MISLBC 297   1 std::rethrow_exception(h_.promise().ep_); 299   std::rethrow_exception(h_.promise().ep_);
298   if constexpr (! std::is_void_v<T>) 300   if constexpr (! std::is_void_v<T>)
HITCBC 299   4 return std::move(*h_.promise().result_); 301   4 return std::move(*h_.promise().result_);
300   else 302   else
MISLBC 301   1 return; 303   return;
302   } 304   }
303   305  
304   /// Start execution with the caller's context. 306   /// Start execution with the caller's context.
HITCBC 305   15 std::coroutine_handle<> await_suspend( 307   13 std::coroutine_handle<> await_suspend(
306   std::coroutine_handle<> cont, 308   std::coroutine_handle<> cont,
307   io_env const* env) 309   io_env const* env)
308   { 310   {
HITCBC 309   15 h_.promise().set_continuation(cont); 311   13 h_.promise().set_continuation(cont);
HITCBC 310   15 h_.promise().set_environment(env); 312   13 h_.promise().set_environment(env);
HITCBC 311   15 return h_; 313   13 return h_;
312   } 314   }
313   315  
314   /** Return the coroutine handle. 316   /** Return the coroutine handle.
315   317  
316   @note Do not call `destroy()` on the returned handle while 318   @note Do not call `destroy()` on the returned handle while
317   the quitter is being awaited. The quitter's lifetime is 319   the quitter is being awaited. The quitter's lifetime is
318   normally managed by `run_async`, `run`, or the awaiting 320   normally managed by `run_async`, `run`, or the awaiting
319   parent; manually destroying a suspended quitter that another 321   parent; manually destroying a suspended quitter that another
320   coroutine is awaiting produces undefined behavior. For 322   coroutine is awaiting produces undefined behavior. For
321   cooperative cancellation, use `std::stop_token`. 323   cooperative cancellation, use `std::stop_token`.
322   324  
323   @return The coroutine handle. 325   @return The coroutine handle.
324   */ 326   */
HITCBC 325   20 std::coroutine_handle<promise_type> handle() const noexcept 327   17 std::coroutine_handle<promise_type> handle() const noexcept
326   { 328   {
HITCBC 327   20 return h_; 329   17 return h_;
328   } 330   }
329   331  
330   /** Release ownership of the coroutine frame. 332   /** Release ownership of the coroutine frame.
331   333  
332   @note If the caller intends to call `destroy()` on the 334   @note If the caller intends to call `destroy()` on the
333   released handle, it must do so only when the quitter has not 335   released handle, it must do so only when the quitter has not
334   started or has fully completed. Destroying a suspended 336   started or has fully completed. Destroying a suspended
335   quitter that is being awaited produces undefined behavior. 337   quitter that is being awaited produces undefined behavior.
336   */ 338   */
HITCBC 337   18 void release() noexcept 339   15 void release() noexcept
338   { 340   {
HITCBC 339   18 h_ = nullptr; 341   15 h_ = nullptr;
HITCBC 340   18 } 342   15 }
341   343  
342   quitter(quitter const&) = delete; 344   quitter(quitter const&) = delete;
343   quitter& operator=(quitter const&) = delete; 345   quitter& operator=(quitter const&) = delete;
344   346  
345   /// Construct by moving, transferring ownership. 347   /// Construct by moving, transferring ownership.
HITCBC 346   49 quitter(quitter&& other) noexcept 348   44 quitter(quitter&& other) noexcept
HITCBC 347   49 : h_(std::exchange(other.h_, nullptr)) 349   44 : h_(std::exchange(other.h_, nullptr))
348   { 350   {
HITCBC 349   49 } 351   44 }
350   352  
351   /// Assign by moving, transferring ownership. 353   /// Assign by moving, transferring ownership.
352   quitter& operator=(quitter&& other) noexcept 354   quitter& operator=(quitter&& other) noexcept
353   { 355   {
354   if(this != &other) 356   if(this != &other)
355   { 357   {
356   if(h_) 358   if(h_)
357   h_.destroy(); 359   h_.destroy();
358   h_ = std::exchange(other.h_, nullptr); 360   h_ = std::exchange(other.h_, nullptr);
359   } 361   }
360   return *this; 362   return *this;
361   } 363   }
362   364  
363   private: 365   private:
HITCBC 364   33 explicit quitter(std::coroutine_handle<promise_type> h) 366   28 explicit quitter(std::coroutine_handle<promise_type> h)
HITCBC 365   33 : h_(h) 367   28 : h_(h)
366   { 368   {
HITCBC 367   33 } 369   28 }
368   }; 370   };
369   371  
370   } // namespace capy 372   } // namespace capy
371   } // namespace boost 373   } // namespace boost
372   374  
373   #endif 375   #endif