89.13% Lines (41/46) 100.00% Functions (8/8)
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_TIMEOUT_HPP 10   #ifndef BOOST_CAPY_TIMEOUT_HPP
11   #define BOOST_CAPY_TIMEOUT_HPP 11   #define BOOST_CAPY_TIMEOUT_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   #include <boost/capy/concept/io_awaitable.hpp> 14   #include <boost/capy/concept/io_awaitable.hpp>
15   #include <boost/capy/delay.hpp> 15   #include <boost/capy/delay.hpp>
16   #include <boost/capy/detail/io_result_combinators.hpp> 16   #include <boost/capy/detail/io_result_combinators.hpp>
17   #include <boost/capy/error.hpp> 17   #include <boost/capy/error.hpp>
18   #include <boost/capy/io_result.hpp> 18   #include <boost/capy/io_result.hpp>
19   #include <boost/capy/task.hpp> 19   #include <boost/capy/task.hpp>
20   #include <boost/capy/when_all.hpp> 20   #include <boost/capy/when_all.hpp>
21   21  
22   #include <atomic> 22   #include <atomic>
23   #include <chrono> 23   #include <chrono>
24   #include <exception> 24   #include <exception>
25   #include <optional> 25   #include <optional>
26   26  
27   namespace boost { 27   namespace boost {
28   namespace capy { 28   namespace capy {
29   namespace detail { 29   namespace detail {
30   30  
31   template<typename T> 31   template<typename T>
32   struct timeout_state 32   struct timeout_state
33   { 33   {
34   when_all_core core_; 34   when_all_core core_;
35   std::atomic<int> winner_{-1}; // -1=none, 0=inner, 1=delay 35   std::atomic<int> winner_{-1}; // -1=none, 0=inner, 1=delay
36   std::optional<T> inner_result_; 36   std::optional<T> inner_result_;
37   std::exception_ptr inner_exception_; 37   std::exception_ptr inner_exception_;
38   std::array<continuation, 2> runner_handles_{}; 38   std::array<continuation, 2> runner_handles_{};
39   39  
HITCBC 40   9 timeout_state() 40   8 timeout_state()
HITCBC 41   9 : core_(2) 41   8 : core_(2)
42   { 42   {
HITCBC 43   9 } 43   8 }
44   }; 44   };
45   45  
46   template<IoAwaitable Awaitable, typename T> 46   template<IoAwaitable Awaitable, typename T>
47   when_all_runner<timeout_state<T>> 47   when_all_runner<timeout_state<T>>
HITCBC 48   9 make_timeout_inner_runner( 48   8 make_timeout_inner_runner(
49   Awaitable inner, timeout_state<T>* state) 49   Awaitable inner, timeout_state<T>* state)
50   { 50   {
51   try 51   try
52   { 52   {
53   auto result = co_await std::move(inner); 53   auto result = co_await std::move(inner);
54   state->inner_result_.emplace(std::move(result)); 54   state->inner_result_.emplace(std::move(result));
55   } 55   }
56   catch(...) 56   catch(...)
57   { 57   {
58   state->inner_exception_ = std::current_exception(); 58   state->inner_exception_ = std::current_exception();
59   } 59   }
60   60  
61   int expected = -1; 61   int expected = -1;
62   if(state->winner_.compare_exchange_strong( 62   if(state->winner_.compare_exchange_strong(
63   expected, 0, std::memory_order_relaxed)) 63   expected, 0, std::memory_order_relaxed))
64   state->core_.stop_source_.request_stop(); 64   state->core_.stop_source_.request_stop();
HITCBC 65   18 } 65   16 }
66   66  
67   template<typename DelayAw, typename T> 67   template<typename DelayAw, typename T>
68   when_all_runner<timeout_state<T>> 68   when_all_runner<timeout_state<T>>
HITCBC 69   9 make_timeout_delay_runner( 69   8 make_timeout_delay_runner(
70   DelayAw d, timeout_state<T>* state) 70   DelayAw d, timeout_state<T>* state)
71   { 71   {
72   auto result = co_await std::move(d); 72   auto result = co_await std::move(d);
73   73  
74   if(!result.ec) 74   if(!result.ec)
75   { 75   {
76   int expected = -1; 76   int expected = -1;
77   if(state->winner_.compare_exchange_strong( 77   if(state->winner_.compare_exchange_strong(
78   expected, 1, std::memory_order_relaxed)) 78   expected, 1, std::memory_order_relaxed))
79   state->core_.stop_source_.request_stop(); 79   state->core_.stop_source_.request_stop();
80   } 80   }
HITCBC 81   18 } 81   16 }
82   82  
83   template<IoAwaitable Inner, typename DelayAw, typename T> 83   template<IoAwaitable Inner, typename DelayAw, typename T>
84   class timeout_launcher 84   class timeout_launcher
85   { 85   {
86   Inner* inner_; 86   Inner* inner_;
87   DelayAw* delay_; 87   DelayAw* delay_;
88   timeout_state<T>* state_; 88   timeout_state<T>* state_;
89   89  
90   public: 90   public:
HITCBC 91   9 timeout_launcher( 91   8 timeout_launcher(
92   Inner* inner, DelayAw* delay, 92   Inner* inner, DelayAw* delay,
93   timeout_state<T>* state) 93   timeout_state<T>* state)
HITCBC 94   9 : inner_(inner) 94   8 : inner_(inner)
HITCBC 95   9 , delay_(delay) 95   8 , delay_(delay)
HITCBC 96   9 , state_(state) 96   8 , state_(state)
97   { 97   {
HITCBC 98   9 } 98   8 }
99   99  
HITCBC 100   9 bool await_ready() const noexcept { return false; } 100   8 bool await_ready() const noexcept { return false; }
101   101  
HITCBC 102   9 std::coroutine_handle<> await_suspend( 102   8 std::coroutine_handle<> await_suspend(
103   std::coroutine_handle<> continuation, 103   std::coroutine_handle<> continuation,
104   io_env const* caller_env) 104   io_env const* caller_env)
105   { 105   {
HITCBC 106   9 state_->core_.continuation_.h = continuation; 106   8 state_->core_.continuation_.h = continuation;
HITCBC 107   9 state_->core_.caller_env_ = caller_env; 107   8 state_->core_.caller_env_ = caller_env;
108   108  
HITCBC 109   9 if(caller_env->stop_token.stop_possible()) 109   8 if(caller_env->stop_token.stop_possible())
110   { 110   {
MISLBC 111   2 state_->core_.parent_stop_callback_.emplace( 111   state_->core_.parent_stop_callback_.emplace(
MISLBC 112   1 caller_env->stop_token, 112   caller_env->stop_token,
113   when_all_core::stop_callback_fn{ 113   when_all_core::stop_callback_fn{
MISLBC 114   1 &state_->core_.stop_source_}); 114   &state_->core_.stop_source_});
115   115  
MISLBC 116   1 if(caller_env->stop_token.stop_requested()) 116   if(caller_env->stop_token.stop_requested())
MISLBC 117   1 state_->core_.stop_source_.request_stop(); 117   state_->core_.stop_source_.request_stop();
118   } 118   }
119   119  
HITCBC 120   9 auto token = state_->core_.stop_source_.get_token(); 120   8 auto token = state_->core_.stop_source_.get_token();
121   121  
HITCBC 122   9 auto r0 = make_timeout_inner_runner( 122   8 auto r0 = make_timeout_inner_runner(
HITCBC 123   9 std::move(*inner_), state_); 123   8 std::move(*inner_), state_);
HITCBC 124   9 auto h0 = r0.release(); 124   8 auto h0 = r0.release();
HITCBC 125   9 h0.promise().state_ = state_; 125   8 h0.promise().state_ = state_;
HITCBC 126   9 h0.promise().env_ = io_env{ 126   8 h0.promise().env_ = io_env{
127   caller_env->executor, token, 127   caller_env->executor, token,
HITCBC 128   9 caller_env->frame_allocator}; 128   8 caller_env->frame_allocator};
HITCBC 129   9 state_->runner_handles_[0].h = 129   8 state_->runner_handles_[0].h =
130   std::coroutine_handle<>{h0}; 130   std::coroutine_handle<>{h0};
131   131  
HITCBC 132   9 auto r1 = make_timeout_delay_runner( 132   8 auto r1 = make_timeout_delay_runner(
HITCBC 133   9 std::move(*delay_), state_); 133   8 std::move(*delay_), state_);
HITCBC 134   9 auto h1 = r1.release(); 134   8 auto h1 = r1.release();
HITCBC 135   9 h1.promise().state_ = state_; 135   8 h1.promise().state_ = state_;
HITCBC 136   9 h1.promise().env_ = io_env{ 136   8 h1.promise().env_ = io_env{
137   caller_env->executor, token, 137   caller_env->executor, token,
HITCBC 138   9 caller_env->frame_allocator}; 138   8 caller_env->frame_allocator};
HITCBC 139   9 state_->runner_handles_[1].h = 139   8 state_->runner_handles_[1].h =
140   std::coroutine_handle<>{h1}; 140   std::coroutine_handle<>{h1};
141   141  
HITCBC 142   9 caller_env->executor.post( 142   8 caller_env->executor.post(
HITCBC 143   9 state_->runner_handles_[0]); 143   8 state_->runner_handles_[0]);
HITCBC 144   9 caller_env->executor.post( 144   8 caller_env->executor.post(
HITCBC 145   9 state_->runner_handles_[1]); 145   8 state_->runner_handles_[1]);
146   146  
HITCBC 147   18 return std::noop_coroutine(); 147   16 return std::noop_coroutine();
HITCBC 148   27 } 148   24 }
149   149  
HITCBC 150   9 void await_resume() const noexcept {} 150   8 void await_resume() const noexcept {}
151   }; 151   };
152   152  
153   } // namespace detail 153   } // namespace detail
154   154  
155   /** Race an io_result-returning awaitable against a deadline. 155   /** Race an io_result-returning awaitable against a deadline.
156   156  
157   Starts the awaitable and a timer concurrently. The first to 157   Starts the awaitable and a timer concurrently. The first to
158   complete wins and cancels the other. If the awaitable finishes 158   complete wins and cancels the other. If the awaitable finishes
159   first, its result is returned as-is (success, error, or 159   first, its result is returned as-is (success, error, or
160   exception). If the timer fires first, an `io_result` with 160   exception). If the timer fires first, an `io_result` with
161   `ec == error::timeout` is produced. The timeout fires at or 161   `ec == error::timeout` is produced. The timeout fires at or
162   after the specified duration. 162   after the specified duration.
163   163  
164   Unlike @ref when_any, exceptions from the inner awaitable 164   Unlike @ref when_any, exceptions from the inner awaitable
165   are always propagated — they are never swallowed by the timer. 165   are always propagated — they are never swallowed by the timer.
166   166  
167   @par Cancellation 167   @par Cancellation
168   168  
169   If the parent's stop token is activated, both children are 169   If the parent's stop token is activated, both children are
170   cancelled. The inner awaitable's cancellation result is 170   cancelled. The inner awaitable's cancellation result is
171   returned. 171   returned.
172   172  
173   @par Example 173   @par Example
174   @code 174   @code
175   auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms); 175   auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
176   if (ec == cond::timeout) { 176   if (ec == cond::timeout) {
177   // handle timeout 177   // handle timeout
178   } 178   }
179   @endcode 179   @endcode
180   180  
181   @tparam A An IoAwaitable returning `io_result<Ts...>`. 181   @tparam A An IoAwaitable returning `io_result<Ts...>`.
182   182  
183   @param a The awaitable to race against the deadline. 183   @param a The awaitable to race against the deadline.
184   @param dur The maximum duration to wait. 184   @param dur The maximum duration to wait.
185   185  
186   @return An `io_result<Ts...>` matching the inner awaitable's 186   @return An `io_result<Ts...>` matching the inner awaitable's
187   result type. On normal completion the inner awaitable's 187   result type. On normal completion the inner awaitable's
188   result is returned unchanged, whether it indicates success 188   result is returned unchanged, whether it indicates success
189   or sets `ec` to an error. On timeout, `ec` is set to 189   or sets `ec` to an error. On timeout, `ec` is set to
190   `error::timeout` and the payload values are 190   `error::timeout` and the payload values are
191   default-initialized. 191   default-initialized.
192   192  
193   @throws Rethrows Any exception from the inner awaitable, 193   @throws Rethrows Any exception from the inner awaitable,
194   regardless of whether the timer has fired. 194   regardless of whether the timer has fired.
195   195  
196   @see delay, cond::timeout 196   @see delay, cond::timeout
197   */ 197   */
198   template<IoAwaitable A, typename Rep, typename Period> 198   template<IoAwaitable A, typename Rep, typename Period>
199   requires detail::is_io_result_v<awaitable_result_t<A>> 199   requires detail::is_io_result_v<awaitable_result_t<A>>
HITCBC 200   9 auto timeout(A a, std::chrono::duration<Rep, Period> dur) 200   8 auto timeout(A a, std::chrono::duration<Rep, Period> dur)
201   -> task<awaitable_result_t<A>> 201   -> task<awaitable_result_t<A>>
202   { 202   {
203   using T = awaitable_result_t<A>; 203   using T = awaitable_result_t<A>;
204   204  
205   auto d = delay(dur); 205   auto d = delay(dur);
206   detail::timeout_state<T> state; 206   detail::timeout_state<T> state;
207   207  
208   co_await detail::timeout_launcher< 208   co_await detail::timeout_launcher<
209   A, decltype(d), T>(&a, &d, &state); 209   A, decltype(d), T>(&a, &d, &state);
210   210  
211   if(state.core_.first_exception_) 211   if(state.core_.first_exception_)
212   std::rethrow_exception(state.core_.first_exception_); 212   std::rethrow_exception(state.core_.first_exception_);
213   if(state.inner_exception_) 213   if(state.inner_exception_)
214   std::rethrow_exception(state.inner_exception_); 214   std::rethrow_exception(state.inner_exception_);
215   215  
216   if(state.winner_.load(std::memory_order_relaxed) == 0) 216   if(state.winner_.load(std::memory_order_relaxed) == 0)
217   co_return std::move(*state.inner_result_); 217   co_return std::move(*state.inner_result_);
218   218  
219   // Delay fired first: timeout 219   // Delay fired first: timeout
220   T r{}; 220   T r{};
221   r.ec = make_error_code(error::timeout); 221   r.ec = make_error_code(error::timeout);
222   co_return r; 222   co_return r;
HITCBC 223   18 } 223   16 }
224   224  
225   } // capy 225   } // capy
226   } // boost 226   } // boost
227   227  
228   #endif 228   #endif