Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
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/corosio
8 : //
9 :
10 : #ifndef BOOST_COROSIO_TIMER_HPP
11 : #define BOOST_COROSIO_TIMER_HPP
12 :
13 : #include <boost/corosio/detail/config.hpp>
14 : #include <boost/corosio/detail/except.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 <concepts>
26 : #include <coroutine>
27 : #include <stop_token>
28 : #include <type_traits>
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 : Each timer operation participates in the affine awaitable protocol,
39 : ensuring coroutines resume on the correct executor.
40 :
41 : @par Thread Safety
42 : Distinct objects: Safe.@n
43 : Shared objects: Unsafe. A timer must not have concurrent wait
44 : operations.
45 :
46 : @par Semantics
47 : Wraps platform timer facilities via the io_context reactor.
48 : Operations dispatch to OS timer APIs (timerfd, IOCP timers,
49 : kqueue EVFILT_TIMER).
50 : */
51 : class BOOST_COROSIO_DECL timer : public io_object
52 : {
53 : struct wait_awaitable
54 : {
55 : timer& t_;
56 : std::stop_token token_;
57 : mutable std::error_code ec_;
58 :
59 5343 : explicit wait_awaitable(timer& t) noexcept : t_(t) {}
60 :
61 5343 : bool await_ready() const noexcept
62 : {
63 5343 : return token_.stop_requested();
64 : }
65 :
66 5343 : capy::io_result<> await_resume() const noexcept
67 : {
68 5343 : if (token_.stop_requested())
69 0 : return {capy::error::canceled};
70 5343 : return {ec_};
71 : }
72 :
73 5343 : auto await_suspend(
74 : std::coroutine_handle<> h,
75 : capy::io_env const* env) -> std::coroutine_handle<>
76 : {
77 5343 : token_ = env->stop_token;
78 5343 : return t_.get().wait(h, env->executor, token_, &ec_);
79 : }
80 : };
81 :
82 : public:
83 : struct timer_impl : io_object_impl
84 : {
85 : virtual std::coroutine_handle<> wait(
86 : std::coroutine_handle<>,
87 : capy::executor_ref,
88 : std::stop_token,
89 : std::error_code*) = 0;
90 : };
91 :
92 : public:
93 : /// The clock type used for time operations.
94 : using clock_type = std::chrono::steady_clock;
95 :
96 : /// The time point type for absolute expiry times.
97 : using time_point = clock_type::time_point;
98 :
99 : /// The duration type for relative expiry times.
100 : using duration = clock_type::duration;
101 :
102 : /** Destructor.
103 :
104 : Cancels any pending operations and releases timer resources.
105 : */
106 : ~timer();
107 :
108 : /** Construct a timer from an execution context.
109 :
110 : @param ctx The execution context that will own this timer.
111 : */
112 : explicit timer(capy::execution_context& ctx);
113 :
114 : /** Move constructor.
115 :
116 : Transfers ownership of the timer resources.
117 :
118 : @param other The timer to move from.
119 : */
120 : timer(timer&& other) noexcept;
121 :
122 : /** Move assignment operator.
123 :
124 : Closes any existing timer and transfers ownership.
125 : The source and destination must share the same execution context.
126 :
127 : @param other The timer to move from.
128 :
129 : @return Reference to this timer.
130 :
131 : @throws std::logic_error if the timers have different execution contexts.
132 : */
133 : timer& operator=(timer&& other);
134 :
135 : timer(timer const&) = delete;
136 : timer& operator=(timer const&) = delete;
137 :
138 : /** Cancel any pending asynchronous operations.
139 :
140 : All outstanding operations complete with an error code that
141 : compares equal to `capy::cond::canceled`.
142 : */
143 : void cancel();
144 :
145 : /** Get the timer's expiry time as an absolute time.
146 :
147 : @return The expiry time point. If no expiry has been set,
148 : returns a default-constructed time_point.
149 : */
150 : time_point expiry() const;
151 :
152 : /** Set the timer's expiry time as an absolute time.
153 :
154 : Any pending asynchronous wait operations will be cancelled.
155 :
156 : @param t The expiry time to be used for the timer.
157 : */
158 : void expires_at(time_point t);
159 :
160 : /** Set the timer's expiry time relative to now.
161 :
162 : Any pending asynchronous wait operations will be cancelled.
163 :
164 : @param d The expiry time relative to now.
165 : */
166 : void expires_after(duration d);
167 :
168 : /** Set the timer's expiry time relative to now.
169 :
170 : This is a convenience overload that accepts any duration type
171 : and converts it to the timer's native duration type.
172 :
173 : @param d The expiry time relative to now.
174 : */
175 : template<class Rep, class Period>
176 5357 : void expires_after(std::chrono::duration<Rep, Period> d)
177 : {
178 5357 : expires_after(std::chrono::duration_cast<duration>(d));
179 5357 : }
180 :
181 : /** Wait for the timer to expire.
182 :
183 : The operation supports cancellation via `std::stop_token` through
184 : the affine awaitable protocol. If the associated stop token is
185 : triggered, the operation completes immediately with an error
186 : that compares equal to `capy::cond::canceled`.
187 :
188 : @par Example
189 : @code
190 : timer t(ctx);
191 : t.expires_after(std::chrono::seconds(5));
192 : auto [ec] = co_await t.wait();
193 : if (ec == capy::cond::canceled)
194 : {
195 : // Cancelled via stop_token or cancel()
196 : co_return;
197 : }
198 : if (ec)
199 : {
200 : // Handle other errors
201 : co_return;
202 : }
203 : // Timer expired
204 : @endcode
205 :
206 : @return An awaitable that completes with `io_result<>`.
207 : Returns success (default error_code) when the timer expires,
208 : or an error code on failure. Compare against error conditions
209 : (e.g., `ec == capy::cond::canceled`) rather than error codes.
210 :
211 : @par Preconditions
212 : The timer must have an expiry time set via expires_at() or
213 : expires_after().
214 : */
215 5343 : auto wait()
216 : {
217 5343 : return wait_awaitable(*this);
218 : }
219 :
220 : private:
221 16124 : timer_impl& get() const noexcept
222 : {
223 16124 : return *static_cast<timer_impl*>(impl_);
224 : }
225 : };
226 :
227 : } // namespace boost::corosio
228 :
229 : #endif
|