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