TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_TIMER_HPP
12 : #define BOOST_COROSIO_TIMER_HPP
13 :
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/io_object.hpp>
16 : #include <boost/capy/io_result.hpp>
17 : #include <boost/capy/error.hpp>
18 : #include <boost/capy/ex/executor_ref.hpp>
19 : #include <boost/capy/ex/execution_context.hpp>
20 : #include <boost/capy/ex/io_env.hpp>
21 : #include <boost/capy/concept/executor.hpp>
22 : #include <system_error>
23 :
24 : #include <chrono>
25 : #include <coroutine>
26 : #include <cstddef>
27 : #include <limits>
28 : #include <stop_token>
29 :
30 : namespace boost::corosio {
31 :
32 : /** An asynchronous timer for coroutine I/O.
33 :
34 : This class provides asynchronous timer operations that return
35 : awaitable types. The timer can be used to schedule operations
36 : to occur after a specified duration or at a specific time point.
37 :
38 : Multiple coroutines may wait concurrently on the same timer.
39 : When the timer expires, all waiters complete with success. When
40 : the timer is cancelled, all waiters complete with an error that
41 : compares equal to `capy::cond::canceled`.
42 :
43 : Each timer operation participates in the affine awaitable protocol,
44 : ensuring coroutines resume on the correct executor.
45 :
46 : @par Thread Safety
47 : Distinct objects: Safe.@n
48 : Shared objects: Unsafe.
49 :
50 : @par Semantics
51 : Wraps platform timer facilities via the io_context reactor.
52 : Operations dispatch to OS timer APIs (timerfd, IOCP timers,
53 : kqueue EVFILT_TIMER).
54 : */
55 : class BOOST_COROSIO_DECL timer : public io_object
56 : {
57 : struct wait_awaitable
58 : {
59 : timer& t_;
60 : std::stop_token token_;
61 : mutable std::error_code ec_;
62 :
63 HIT 9111 : explicit wait_awaitable(timer& t) noexcept : t_(t) {}
64 :
65 9111 : bool await_ready() const noexcept
66 : {
67 9111 : return token_.stop_requested();
68 : }
69 :
70 9111 : capy::io_result<> await_resume() const noexcept
71 : {
72 9111 : if (token_.stop_requested())
73 MIS 0 : return {capy::error::canceled};
74 HIT 9111 : return {ec_};
75 : }
76 :
77 9111 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
78 : -> std::coroutine_handle<>
79 : {
80 9111 : token_ = env->stop_token;
81 9111 : auto& impl = t_.get();
82 : // Inline fast path: already expired and not in the heap
83 18200 : if (impl.heap_index_ == implementation::npos &&
84 18174 : (impl.expiry_ == (time_point::min)() ||
85 18196 : impl.expiry_ <= clock_type::now()))
86 : {
87 260 : ec_ = {};
88 260 : auto d = env->executor;
89 260 : d.post(h);
90 260 : return std::noop_coroutine();
91 : }
92 8851 : return impl.wait(h, env->executor, std::move(token_), &ec_);
93 : }
94 : };
95 :
96 : public:
97 : struct implementation : io_object::implementation
98 : {
99 : static constexpr std::size_t npos =
100 : (std::numeric_limits<std::size_t>::max)();
101 :
102 : std::chrono::steady_clock::time_point expiry_{};
103 : std::size_t heap_index_ = npos;
104 : bool might_have_pending_waits_ = false;
105 :
106 : virtual std::coroutine_handle<> wait(
107 : std::coroutine_handle<>,
108 : capy::executor_ref,
109 : std::stop_token,
110 : std::error_code*) = 0;
111 : };
112 :
113 : public:
114 : /// The clock type used for time operations.
115 : using clock_type = std::chrono::steady_clock;
116 :
117 : /// The time point type for absolute expiry times.
118 : using time_point = clock_type::time_point;
119 :
120 : /// The duration type for relative expiry times.
121 : using duration = clock_type::duration;
122 :
123 : /** Destructor.
124 :
125 : Cancels any pending operations and releases timer resources.
126 : */
127 : ~timer() override;
128 :
129 : /** Construct a timer from an execution context.
130 :
131 : @param ctx The execution context that will own this timer.
132 : */
133 : explicit timer(capy::execution_context& ctx);
134 :
135 : /** Construct a timer with an initial absolute expiry time.
136 :
137 : @param ctx The execution context that will own this timer.
138 : @param t The initial expiry time point.
139 : */
140 : timer(capy::execution_context& ctx, time_point t);
141 :
142 : /** Construct a timer with an initial relative expiry time.
143 :
144 : @param ctx The execution context that will own this timer.
145 : @param d The initial expiry duration relative to now.
146 : */
147 : template<class Rep, class Period>
148 2 : timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
149 2 : : timer(ctx)
150 : {
151 2 : expires_after(d);
152 2 : }
153 :
154 : /** Move constructor.
155 :
156 : Transfers ownership of the timer resources.
157 :
158 : @param other The timer to move from.
159 : */
160 : timer(timer&& other) noexcept;
161 :
162 : /** Move assignment operator.
163 :
164 : Closes any existing timer and transfers ownership.
165 :
166 : @param other The timer to move from.
167 :
168 : @return Reference to this timer.
169 : */
170 : timer& operator=(timer&& other) noexcept;
171 :
172 : timer(timer const&) = delete;
173 : timer& operator=(timer const&) = delete;
174 :
175 : /** Cancel all pending asynchronous wait operations.
176 :
177 : All outstanding operations complete with an error code that
178 : compares equal to `capy::cond::canceled`.
179 :
180 : @return The number of operations that were cancelled.
181 : */
182 20 : std::size_t cancel()
183 : {
184 20 : if (!get().might_have_pending_waits_)
185 12 : return 0;
186 8 : return do_cancel();
187 : }
188 :
189 : /** Cancel one pending asynchronous wait operation.
190 :
191 : The oldest pending wait is cancelled (FIFO order). It
192 : completes with an error code that compares equal to
193 : `capy::cond::canceled`.
194 :
195 : @return The number of operations that were cancelled (0 or 1).
196 : */
197 4 : std::size_t cancel_one()
198 : {
199 4 : if (!get().might_have_pending_waits_)
200 2 : return 0;
201 2 : return do_cancel_one();
202 : }
203 :
204 : /** Return the timer's expiry time as an absolute time.
205 :
206 : @return The expiry time point. If no expiry has been set,
207 : returns a default-constructed time_point.
208 : */
209 38 : time_point expiry() const noexcept
210 : {
211 38 : return get().expiry_;
212 : }
213 :
214 : /** Set the timer's expiry time as an absolute time.
215 :
216 : Any pending asynchronous wait operations will be cancelled.
217 :
218 : @param t The expiry time to be used for the timer.
219 :
220 : @return The number of pending operations that were cancelled.
221 : */
222 18 : std::size_t expires_at(time_point t)
223 : {
224 18 : auto& impl = get();
225 18 : impl.expiry_ = t;
226 18 : if (impl.heap_index_ == implementation::npos &&
227 16 : !impl.might_have_pending_waits_)
228 16 : return 0;
229 2 : return do_update_expiry();
230 : }
231 :
232 : /** Set the timer's expiry time relative to now.
233 :
234 : Any pending asynchronous wait operations will be cancelled.
235 :
236 : @param d The expiry time relative to now.
237 :
238 : @return The number of pending operations that were cancelled.
239 : */
240 9113 : std::size_t expires_after(duration d)
241 : {
242 9113 : auto& impl = get();
243 9113 : if (d <= duration::zero())
244 6 : impl.expiry_ = (time_point::min)();
245 : else
246 9107 : impl.expiry_ = clock_type::now() + d;
247 9113 : if (impl.heap_index_ == implementation::npos &&
248 9109 : !impl.might_have_pending_waits_)
249 9109 : return 0;
250 4 : return do_update_expiry();
251 : }
252 :
253 : /** Set the timer's expiry time relative to now.
254 :
255 : This is a convenience overload that accepts any duration type
256 : and converts it to the timer's native duration type. Any
257 : pending asynchronous wait operations will be cancelled.
258 :
259 : @param d The expiry time relative to now.
260 :
261 : @return The number of pending operations that were cancelled.
262 : */
263 : template<class Rep, class Period>
264 9113 : std::size_t expires_after(std::chrono::duration<Rep, Period> d)
265 : {
266 9113 : return expires_after(std::chrono::duration_cast<duration>(d));
267 : }
268 :
269 : /** Wait for the timer to expire.
270 :
271 : Multiple coroutines may wait on the same timer concurrently.
272 : When the timer expires, all waiters complete with success.
273 :
274 : The operation supports cancellation via `std::stop_token` through
275 : the affine awaitable protocol. If the associated stop token is
276 : triggered, only that waiter completes with an error that
277 : compares equal to `capy::cond::canceled`; other waiters are
278 : unaffected.
279 :
280 : @par Example
281 : @code
282 : timer t(ctx);
283 : t.expires_after(std::chrono::seconds(5));
284 : auto [ec] = co_await t.wait();
285 : if (ec == capy::cond::canceled)
286 : {
287 : // Cancelled via stop_token or cancel()
288 : co_return;
289 : }
290 : if (ec)
291 : {
292 : // Handle other errors
293 : co_return;
294 : }
295 : // Timer expired
296 : @endcode
297 :
298 : @return An awaitable that completes with `io_result<>`.
299 : Returns success (default error_code) when the timer expires,
300 : or an error code on failure. Compare against error conditions
301 : (e.g., `ec == capy::cond::canceled`) rather than error codes.
302 :
303 : @par Preconditions
304 : The timer must have an expiry time set via expires_at() or
305 : expires_after().
306 : */
307 9111 : auto wait()
308 : {
309 9111 : return wait_awaitable(*this);
310 : }
311 :
312 : private:
313 : // Out-of-line cancel/expiry when inline fast-path
314 : // conditions (no waiters, not in heap) are not met.
315 : std::size_t do_cancel();
316 : std::size_t do_cancel_one();
317 : std::size_t do_update_expiry();
318 :
319 : /// Return the underlying implementation.
320 18320 : implementation& get() const noexcept
321 : {
322 18320 : return *static_cast<implementation*>(h_.get());
323 : }
324 : };
325 :
326 : } // namespace boost::corosio
327 :
328 : #endif
|