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