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/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 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 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 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
|