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   8 timeout_state() 40   8 timeout_state()
HITCBC 41   8 : core_(2) 41   8 : core_(2)
42   { 42   {
HITCBC 43   8 } 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   8 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   16 } 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   8 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   16 } 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   8 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   8 : inner_(inner) 94   8 : inner_(inner)
HITCBC 95   8 , delay_(delay) 95   8 , delay_(delay)
HITCBC 96   8 , state_(state) 96   8 , state_(state)
97   { 97   {
HITCBC 98   8 } 98   8 }
99   99  
HITCBC 100   8 bool await_ready() const noexcept { return false; } 100   8 bool await_ready() const noexcept { return false; }
101   101  
HITCBC 102   8 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   8 state_->core_.continuation_.h = continuation; 106   8 state_->core_.continuation_.h = continuation;
HITCBC 107   8 state_->core_.caller_env_ = caller_env; 107   8 state_->core_.caller_env_ = caller_env;
108   108  
HITCBC 109   8 if(caller_env->stop_token.stop_possible()) 109   8 if(caller_env->stop_token.stop_possible())
110   { 110   {
MISUBC 111   state_->core_.parent_stop_callback_.emplace( 111   state_->core_.parent_stop_callback_.emplace(
MISUBC 112   caller_env->stop_token, 112   caller_env->stop_token,
113   when_all_core::stop_callback_fn{ 113   when_all_core::stop_callback_fn{
MISUBC 114   &state_->core_.stop_source_}); 114   &state_->core_.stop_source_});
115   115  
MISUBC 116   if(caller_env->stop_token.stop_requested()) 116   if(caller_env->stop_token.stop_requested())
MISUBC 117   state_->core_.stop_source_.request_stop(); 117   state_->core_.stop_source_.request_stop();
118   } 118   }
119   119  
HITCBC 120   8 auto token = state_->core_.stop_source_.get_token(); 120   8 auto token = state_->core_.stop_source_.get_token();
121   121  
HITCBC 122   8 auto r0 = make_timeout_inner_runner( 122   8 auto r0 = make_timeout_inner_runner(
HITCBC 123   8 std::move(*inner_), state_); 123   8 std::move(*inner_), state_);
HITCBC 124   8 auto h0 = r0.release(); 124   8 auto h0 = r0.release();
HITCBC 125   8 h0.promise().state_ = state_; 125   8 h0.promise().state_ = state_;
HITCBC 126   8 h0.promise().env_ = io_env{ 126   8 h0.promise().env_ = io_env{
127   caller_env->executor, token, 127   caller_env->executor, token,
HITCBC 128   8 caller_env->frame_allocator}; 128   8 caller_env->frame_allocator};
HITCBC 129   8 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   8 auto r1 = make_timeout_delay_runner( 132   8 auto r1 = make_timeout_delay_runner(
HITCBC 133   8 std::move(*delay_), state_); 133   8 std::move(*delay_), state_);
HITCBC 134   8 auto h1 = r1.release(); 134   8 auto h1 = r1.release();
HITCBC 135   8 h1.promise().state_ = state_; 135   8 h1.promise().state_ = state_;
HITCBC 136   8 h1.promise().env_ = io_env{ 136   8 h1.promise().env_ = io_env{
137   caller_env->executor, token, 137   caller_env->executor, token,
HITCBC 138   8 caller_env->frame_allocator}; 138   8 caller_env->frame_allocator};
HITCBC 139   8 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   8 caller_env->executor.post( 142   8 caller_env->executor.post(
HITCBC 143   8 state_->runner_handles_[0]); 143   8 state_->runner_handles_[0]);
HITCBC 144   8 caller_env->executor.post( 144   8 caller_env->executor.post(
HITCBC 145   8 state_->runner_handles_[1]); 145   8 state_->runner_handles_[1]);
146   146  
HITCBC 147   16 return std::noop_coroutine(); 147   16 return std::noop_coroutine();
HITCBC 148   24 } 148   24 }
149   149  
HITCBC 150   8 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. 161   `ec == error::timeout` is produced.
162   162  
163   Unlike @ref when_any, exceptions from the inner awaitable 163   Unlike @ref when_any, exceptions from the inner awaitable
164   are always propagated — they are never swallowed by the timer. 164   are always propagated — they are never swallowed by the timer.
165   165  
166   @par Return Type 166   @par Return Type
167   167  
168   Always returns `io_result<Ts...>` matching the inner 168   Always returns `io_result<Ts...>` matching the inner
169   awaitable's result type. On timeout, `ec` is set to 169   awaitable's result type. On timeout, `ec` is set to
170   `error::timeout` and payload values are default-initialized. 170   `error::timeout` and payload values are default-initialized.
171   171  
172   @par Precision 172   @par Precision
173   173  
174   The timeout fires at or after the specified duration. 174   The timeout fires at or after the specified duration.
175   175  
176   @par Cancellation 176   @par Cancellation
177   177  
178   If the parent's stop token is activated, both children are 178   If the parent's stop token is activated, both children are
179   cancelled. The inner awaitable's cancellation result is 179   cancelled. The inner awaitable's cancellation result is
180   returned. 180   returned.
181   181  
182   @par Example 182   @par Example
183   @code 183   @code
184   auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms); 184   auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms);
185   if (ec == cond::timeout) { 185   if (ec == cond::timeout) {
186   // handle timeout 186   // handle timeout
187   } 187   }
188   @endcode 188   @endcode
189   189  
190   @tparam A An IoAwaitable returning `io_result<Ts...>`. 190   @tparam A An IoAwaitable returning `io_result<Ts...>`.
191   191  
192   @param a The awaitable to race against the deadline. 192   @param a The awaitable to race against the deadline.
193   @param dur The maximum duration to wait. 193   @param dur The maximum duration to wait.
194   194  
195   @return `task<awaitable_result_t<A>>`. 195   @return `task<awaitable_result_t<A>>`.
196   196  
197   @throws Rethrows any exception from the inner awaitable, 197   @throws Rethrows any exception from the inner awaitable,
198   regardless of whether the timer has fired. 198   regardless of whether the timer has fired.
199   199  
200   @see delay, cond::timeout 200   @see delay, cond::timeout
201   */ 201   */
202   template<IoAwaitable A, typename Rep, typename Period> 202   template<IoAwaitable A, typename Rep, typename Period>
203   requires detail::is_io_result_v<awaitable_result_t<A>> 203   requires detail::is_io_result_v<awaitable_result_t<A>>
HITCBC 204   8 auto timeout(A a, std::chrono::duration<Rep, Period> dur) 204   8 auto timeout(A a, std::chrono::duration<Rep, Period> dur)
205   -> task<awaitable_result_t<A>> 205   -> task<awaitable_result_t<A>>
206   { 206   {
207   using T = awaitable_result_t<A>; 207   using T = awaitable_result_t<A>;
208   208  
209   auto d = delay(dur); 209   auto d = delay(dur);
210   detail::timeout_state<T> state; 210   detail::timeout_state<T> state;
211   211  
212   co_await detail::timeout_launcher< 212   co_await detail::timeout_launcher<
213   A, decltype(d), T>(&a, &d, &state); 213   A, decltype(d), T>(&a, &d, &state);
214   214  
215   if(state.core_.first_exception_) 215   if(state.core_.first_exception_)
216   std::rethrow_exception(state.core_.first_exception_); 216   std::rethrow_exception(state.core_.first_exception_);
217   if(state.inner_exception_) 217   if(state.inner_exception_)
218   std::rethrow_exception(state.inner_exception_); 218   std::rethrow_exception(state.inner_exception_);
219   219  
220   if(state.winner_.load(std::memory_order_relaxed) == 0) 220   if(state.winner_.load(std::memory_order_relaxed) == 0)
221   co_return std::move(*state.inner_result_); 221   co_return std::move(*state.inner_result_);
222   222  
223   // Delay fired first: timeout 223   // Delay fired first: timeout
224   T r{}; 224   T r{};
225   r.ec = make_error_code(error::timeout); 225   r.ec = make_error_code(error::timeout);
226   co_return r; 226   co_return r;
HITCBC 227   16 } 227   16 }
228   228  
229   } // capy 229   } // capy
230   } // boost 230   } // boost
231   231  
232   #endif 232   #endif