libs/corosio/include/boost/corosio/timer.hpp

100.0% Lines (20/20) 100.0% Functions (11/11) 100.0% Branches (4/4)
libs/corosio/include/boost/corosio/timer.hpp
Line Branch Hits 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/detail/except.hpp>
16 #include <boost/corosio/io_object.hpp>
17 #include <boost/capy/io_result.hpp>
18 #include <boost/capy/error.hpp>
19 #include <boost/capy/ex/executor_ref.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include <boost/capy/ex/io_env.hpp>
22 #include <boost/capy/concept/executor.hpp>
23 #include <system_error>
24
25 #include <chrono>
26 #include <coroutine>
27 #include <cstddef>
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 8769 explicit wait_awaitable(timer& t) noexcept : t_(t) {}
64
65 8769 bool await_ready() const noexcept
66 {
67 8769 return token_.stop_requested();
68 }
69
70 8769 capy::io_result<> await_resume() const noexcept
71 {
72
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 8765 times.
8769 if (token_.stop_requested())
73 4 return {capy::error::canceled};
74 8765 return {ec_};
75 }
76
77 8769 auto await_suspend(
78 std::coroutine_handle<> h,
79 capy::io_env const* env) -> std::coroutine_handle<>
80 {
81 8769 token_ = env->stop_token;
82
1/1
✓ Branch 3 taken 8769 times.
8769 return t_.get().wait(h, env->executor, token_, &ec_);
83 }
84 };
85
86 public:
87 struct timer_impl : io_object_impl
88 {
89 virtual std::coroutine_handle<> wait(
90 std::coroutine_handle<>,
91 capy::executor_ref,
92 std::stop_token,
93 std::error_code*) = 0;
94 };
95
96 public:
97 /// The clock type used for time operations.
98 using clock_type = std::chrono::steady_clock;
99
100 /// The time point type for absolute expiry times.
101 using time_point = clock_type::time_point;
102
103 /// The duration type for relative expiry times.
104 using duration = clock_type::duration;
105
106 /** Destructor.
107
108 Cancels any pending operations and releases timer resources.
109 */
110 ~timer();
111
112 /** Construct a timer from an execution context.
113
114 @param ctx The execution context that will own this timer.
115 */
116 explicit timer(capy::execution_context& ctx);
117
118 /** Construct a timer with an initial absolute expiry time.
119
120 @param ctx The execution context that will own this timer.
121 @param t The initial expiry time point.
122 */
123 timer(capy::execution_context& ctx, time_point t);
124
125 /** Construct a timer with an initial relative expiry time.
126
127 @param ctx The execution context that will own this timer.
128 @param d The initial expiry duration relative to now.
129 */
130 template<class Rep, class Period>
131 2 timer(
132 capy::execution_context& ctx,
133 std::chrono::duration<Rep, Period> d)
134 2 : timer(ctx)
135 {
136
1/1
✓ Branch 1 taken 2 times.
2 expires_after(d);
137 2 }
138
139 /** Move constructor.
140
141 Transfers ownership of the timer resources.
142
143 @param other The timer to move from.
144 */
145 timer(timer&& other) noexcept;
146
147 /** Move assignment operator.
148
149 Closes any existing timer and transfers ownership.
150 The source and destination must share the same execution context.
151
152 @param other The timer to move from.
153
154 @return Reference to this timer.
155
156 @throws std::logic_error if the timers have different execution contexts.
157 */
158 timer& operator=(timer&& other);
159
160 timer(timer const&) = delete;
161 timer& operator=(timer const&) = delete;
162
163 /** Cancel all pending asynchronous wait operations.
164
165 All outstanding operations complete with an error code that
166 compares equal to `capy::cond::canceled`.
167
168 @return The number of operations that were cancelled.
169 */
170 std::size_t cancel();
171
172 /** Cancel one pending asynchronous wait operation.
173
174 The oldest pending wait is cancelled (FIFO order). It
175 completes with an error code that compares equal to
176 `capy::cond::canceled`.
177
178 @return The number of operations that were cancelled (0 or 1).
179 */
180 std::size_t cancel_one();
181
182 /** Return the timer's expiry time as an absolute time.
183
184 @return The expiry time point. If no expiry has been set,
185 returns a default-constructed time_point.
186 */
187 time_point expiry() const;
188
189 /** Set the timer's expiry time as an absolute time.
190
191 Any pending asynchronous wait operations will be cancelled.
192
193 @param t The expiry time to be used for the timer.
194
195 @return The number of pending operations that were cancelled.
196 */
197 std::size_t expires_at(time_point t);
198
199 /** Set the timer's expiry time relative to now.
200
201 Any pending asynchronous wait operations will be cancelled.
202
203 @param d The expiry time relative to now.
204
205 @return The number of pending operations that were cancelled.
206 */
207 std::size_t expires_after(duration d);
208
209 /** Set the timer's expiry time relative to now.
210
211 This is a convenience overload that accepts any duration type
212 and converts it to the timer's native duration type. Any
213 pending asynchronous wait operations will be cancelled.
214
215 @param d The expiry time relative to now.
216
217 @return The number of pending operations that were cancelled.
218 */
219 template<class Rep, class Period>
220 8769 std::size_t expires_after(std::chrono::duration<Rep, Period> d)
221 {
222 8769 return expires_after(std::chrono::duration_cast<duration>(d));
223 }
224
225 /** Wait for the timer to expire.
226
227 Multiple coroutines may wait on the same timer concurrently.
228 When the timer expires, all waiters complete with success.
229
230 The operation supports cancellation via `std::stop_token` through
231 the affine awaitable protocol. If the associated stop token is
232 triggered, only that waiter completes with an error that
233 compares equal to `capy::cond::canceled`; other waiters are
234 unaffected.
235
236 @par Example
237 @code
238 timer t(ctx);
239 t.expires_after(std::chrono::seconds(5));
240 auto [ec] = co_await t.wait();
241 if (ec == capy::cond::canceled)
242 {
243 // Cancelled via stop_token or cancel()
244 co_return;
245 }
246 if (ec)
247 {
248 // Handle other errors
249 co_return;
250 }
251 // Timer expired
252 @endcode
253
254 @return An awaitable that completes with `io_result<>`.
255 Returns success (default error_code) when the timer expires,
256 or an error code on failure. Compare against error conditions
257 (e.g., `ec == capy::cond::canceled`) rather than error codes.
258
259 @par Preconditions
260 The timer must have an expiry time set via expires_at() or
261 expires_after().
262 */
263 8769 auto wait()
264 {
265 8769 return wait_awaitable(*this);
266 }
267
268 private:
269 26394 timer_impl& get() const noexcept
270 {
271 26394 return *static_cast<timer_impl*>(impl_);
272 }
273 };
274
275 } // namespace boost::corosio
276
277 #endif
278