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 5157 : explicit wait_awaitable(timer& t) noexcept : t_(t) {}
59 :
60 5157 : bool await_ready() const noexcept
61 : {
62 5157 : return token_.stop_requested();
63 : }
64 :
65 5157 : capy::io_result<> await_resume() const noexcept
66 : {
67 5157 : if (token_.stop_requested())
68 0 : return {capy::error::canceled};
69 5157 : 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 : t_.get().wait(h, ex, token_, &ec_);
78 : return std::noop_coroutine();
79 : }
80 :
81 : template<typename Ex>
82 5157 : auto await_suspend(
83 : std::coroutine_handle<> h,
84 : Ex const& ex,
85 : std::stop_token token) -> std::coroutine_handle<>
86 : {
87 5157 : token_ = std::move(token);
88 5157 : t_.get().wait(h, ex, token_, &ec_);
89 5157 : return std::noop_coroutine();
90 : }
91 : };
92 :
93 : public:
94 : struct timer_impl : io_object_impl
95 : {
96 : virtual void wait(
97 : std::coroutine_handle<>,
98 : capy::executor_ref,
99 : std::stop_token,
100 : std::error_code*) = 0;
101 : };
102 :
103 : public:
104 : /// The clock type used for time operations.
105 : using clock_type = std::chrono::steady_clock;
106 :
107 : /// The time point type for absolute expiry times.
108 : using time_point = clock_type::time_point;
109 :
110 : /// The duration type for relative expiry times.
111 : using duration = clock_type::duration;
112 :
113 : /** Destructor.
114 :
115 : Cancels any pending operations and releases timer resources.
116 : */
117 : ~timer();
118 :
119 : /** Construct a timer from an execution context.
120 :
121 : @param ctx The execution context that will own this timer.
122 : */
123 : explicit timer(capy::execution_context& ctx);
124 :
125 : /** Move constructor.
126 :
127 : Transfers ownership of the timer resources.
128 :
129 : @param other The timer to move from.
130 : */
131 : timer(timer&& other) noexcept;
132 :
133 : /** Move assignment operator.
134 :
135 : Closes any existing timer and transfers ownership.
136 : The source and destination must share the same execution context.
137 :
138 : @param other The timer to move from.
139 :
140 : @return Reference to this timer.
141 :
142 : @throws std::logic_error if the timers have different execution contexts.
143 : */
144 : timer& operator=(timer&& other);
145 :
146 : timer(timer const&) = delete;
147 : timer& operator=(timer const&) = delete;
148 :
149 : /** Cancel any pending asynchronous operations.
150 :
151 : All outstanding operations complete with an error code that
152 : compares equal to `capy::cond::canceled`.
153 : */
154 : void cancel();
155 :
156 : /** Get the timer's expiry time as an absolute time.
157 :
158 : @return The expiry time point. If no expiry has been set,
159 : returns a default-constructed time_point.
160 : */
161 : time_point expiry() const;
162 :
163 : /** Set the timer's expiry time as an absolute time.
164 :
165 : Any pending asynchronous wait operations will be cancelled.
166 :
167 : @param t The expiry time to be used for the timer.
168 : */
169 : void expires_at(time_point t);
170 :
171 : /** Set the timer's expiry time relative to now.
172 :
173 : Any pending asynchronous wait operations will be cancelled.
174 :
175 : @param d The expiry time relative to now.
176 : */
177 : void expires_after(duration d);
178 :
179 : /** Set the timer's expiry time relative to now.
180 :
181 : This is a convenience overload that accepts any duration type
182 : and converts it to the timer's native duration type.
183 :
184 : @param d The expiry time relative to now.
185 : */
186 : template<class Rep, class Period>
187 5171 : void expires_after(std::chrono::duration<Rep, Period> d)
188 : {
189 5171 : expires_after(std::chrono::duration_cast<duration>(d));
190 5171 : }
191 :
192 : /** Wait for the timer to expire.
193 :
194 : The operation supports cancellation via `std::stop_token` through
195 : the affine awaitable protocol. If the associated stop token is
196 : triggered, the operation completes immediately with an error
197 : that compares equal to `capy::cond::canceled`.
198 :
199 : @par Example
200 : @code
201 : timer t(ctx);
202 : t.expires_after(std::chrono::seconds(5));
203 : auto [ec] = co_await t.wait();
204 : if (ec == capy::cond::canceled)
205 : {
206 : // Cancelled via stop_token or cancel()
207 : co_return;
208 : }
209 : if (ec)
210 : {
211 : // Handle other errors
212 : co_return;
213 : }
214 : // Timer expired
215 : @endcode
216 :
217 : @return An awaitable that completes with `io_result<>`.
218 : Returns success (default error_code) when the timer expires,
219 : or an error code on failure. Compare against error conditions
220 : (e.g., `ec == capy::cond::canceled`) rather than error codes.
221 :
222 : @par Preconditions
223 : The timer must have an expiry time set via expires_at() or
224 : expires_after().
225 : */
226 5157 : auto wait()
227 : {
228 5157 : return wait_awaitable(*this);
229 : }
230 :
231 : private:
232 15566 : timer_impl& get() const noexcept
233 : {
234 15566 : return *static_cast<timer_impl*>(impl_);
235 : }
236 : };
237 :
238 : } // namespace boost::corosio
239 :
240 : #endif
|