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