TLA 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/io/io_timer.hpp>
16 : #include <boost/capy/ex/execution_context.hpp>
17 : #include <boost/capy/concept/executor.hpp>
18 :
19 : #include <chrono>
20 : #include <concepts>
21 : #include <cstddef>
22 : #include <type_traits>
23 :
24 : namespace boost::corosio {
25 :
26 : /** An asynchronous timer for coroutine I/O.
27 :
28 : This class provides asynchronous timer operations that return
29 : awaitable types. The timer can be used to schedule operations
30 : to occur after a specified duration or at a specific time point.
31 :
32 : Multiple coroutines may wait concurrently on the same timer.
33 : When the timer expires, all waiters complete with success. When
34 : the timer is cancelled, all waiters complete with an error that
35 : compares equal to `capy::cond::canceled`.
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.
43 :
44 : @par Semantics
45 : Timers are not backed by per-timer kernel objects. The io_context's
46 : timer service keeps a process-side min-heap of pending expirations;
47 : the nearest expiry drives the reactor's poll timeout, and expirations
48 : are processed in the run loop.
49 : */
50 : class BOOST_COROSIO_DECL timer : public io_timer
51 : {
52 : public:
53 : /// Alias for backward compatibility.
54 : using implementation = io_timer::implementation;
55 :
56 : /** Destructor.
57 :
58 : Cancels any pending operations and releases timer resources.
59 : */
60 : ~timer() override;
61 :
62 : /** Construct a timer from an execution context.
63 :
64 : @param ctx The execution context that will own this timer. It
65 : must be a corosio io_context; otherwise the constructor
66 : throws (a timer service is required).
67 :
68 : @throws std::logic_error if @p ctx is not an io_context.
69 : */
70 : explicit timer(capy::execution_context& ctx);
71 :
72 : /** Construct a timer with an initial absolute expiry time.
73 :
74 : @param ctx The execution context that will own this timer. It
75 : must be a corosio io_context; otherwise the constructor
76 : throws (a timer service is required).
77 : @param t The initial expiry time point.
78 :
79 : @throws std::logic_error if @p ctx is not an io_context.
80 : */
81 : timer(capy::execution_context& ctx, time_point t);
82 :
83 : /** Construct a timer with an initial relative expiry time.
84 :
85 : @param ctx The execution context that will own this timer. It
86 : must be a corosio io_context; otherwise the constructor
87 : throws (a timer service is required).
88 : @param d The initial expiry duration relative to now.
89 :
90 : @throws std::logic_error if @p ctx is not an io_context.
91 : */
92 : template<class Rep, class Period>
93 HIT 8 : timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
94 8 : : timer(ctx)
95 : {
96 8 : expires_after(d);
97 8 : }
98 :
99 : /** Construct a timer from an executor.
100 :
101 : The timer is associated with the executor's context, which must
102 : be a corosio io_context.
103 :
104 : @param ex The executor whose context will own this timer.
105 :
106 : @throws std::logic_error if the executor's context is not an
107 : io_context.
108 : */
109 : template<class Ex>
110 : requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) &&
111 : capy::Executor<Ex>
112 4 : explicit timer(Ex const& ex) : timer(ex.context())
113 : {
114 2 : }
115 :
116 : /** Construct a timer from an executor with an absolute expiry time.
117 :
118 : @param ex The executor whose context will own this timer.
119 : @param t The initial expiry time point.
120 :
121 : @throws std::logic_error if the executor's context is not an
122 : io_context.
123 : */
124 : template<class Ex>
125 : requires capy::Executor<Ex>
126 2 : timer(Ex const& ex, time_point t) : timer(ex.context(), t)
127 : {
128 2 : }
129 :
130 : /** Construct a timer from an executor with a relative expiry time.
131 :
132 : @param ex The executor whose context will own this timer.
133 : @param d The initial expiry duration relative to now.
134 :
135 : @throws std::logic_error if the executor's context is not an
136 : io_context.
137 : */
138 : template<class Ex, class Rep, class Period>
139 : requires capy::Executor<Ex>
140 2 : timer(Ex const& ex, std::chrono::duration<Rep, Period> d)
141 2 : : timer(ex.context(), d)
142 : {
143 2 : }
144 :
145 : /** Move constructor.
146 :
147 : Transfers ownership of the timer resources.
148 :
149 : @param other The timer to move from.
150 :
151 : @pre No awaitables returned by @p other's methods exist.
152 : @pre The execution context associated with @p other must
153 : outlive this timer.
154 : */
155 : timer(timer&& other) noexcept;
156 :
157 : /** Move assignment operator.
158 :
159 : Closes any existing timer and transfers ownership.
160 :
161 : @param other The timer to move from.
162 :
163 : @pre No awaitables returned by either `*this` or @p other's
164 : methods exist.
165 : @pre The execution context associated with @p other must
166 : outlive this timer.
167 :
168 : @return Reference to this timer.
169 : */
170 : timer& operator=(timer&& other) noexcept;
171 :
172 : timer(timer const&) = delete;
173 : timer& operator=(timer const&) = delete;
174 :
175 : /** Cancel one pending asynchronous wait operation.
176 :
177 : The oldest pending wait is cancelled (FIFO order). It
178 : completes with an error code that compares equal to
179 : `capy::cond::canceled`.
180 :
181 : @return The number of operations that were cancelled (0 or 1).
182 : */
183 4 : std::size_t cancel_one()
184 : {
185 4 : if (!get().might_have_pending_waits_)
186 2 : return 0;
187 2 : return do_cancel_one();
188 : }
189 :
190 : /** Set the timer's expiry time as an absolute time.
191 :
192 : Any pending asynchronous wait operations will be cancelled.
193 :
194 : @param t The expiry time to be used for the timer.
195 :
196 : @return The number of pending operations that were cancelled.
197 : */
198 54 : std::size_t expires_at(time_point t)
199 : {
200 54 : auto& impl = get();
201 54 : impl.expiry_ = t;
202 54 : if (impl.heap_index_ == implementation::npos &&
203 50 : !impl.might_have_pending_waits_)
204 50 : return 0;
205 4 : return do_update_expiry();
206 : }
207 :
208 : /** Set the timer's expiry time relative to now.
209 :
210 : Any pending asynchronous wait operations will be cancelled.
211 :
212 : @param d The expiry time relative to now.
213 :
214 : @return The number of pending operations that were cancelled.
215 : */
216 8432 : std::size_t expires_after(duration d)
217 : {
218 8432 : auto& impl = get();
219 8432 : if (d <= duration::zero())
220 6 : impl.expiry_ = (time_point::min)();
221 : else
222 8426 : impl.expiry_ = clock_type::now() + d;
223 8432 : if (impl.heap_index_ == implementation::npos &&
224 8428 : !impl.might_have_pending_waits_)
225 8428 : return 0;
226 4 : return do_update_expiry();
227 : }
228 :
229 : /** Set the timer's expiry time relative to now.
230 :
231 : This is a convenience overload that accepts any duration type
232 : and converts it to the timer's native duration type. Any
233 : pending asynchronous wait operations will be cancelled.
234 :
235 : @param d The expiry time relative to now.
236 :
237 : @return The number of pending operations that were cancelled.
238 : */
239 : template<class Rep, class Period>
240 8432 : std::size_t expires_after(std::chrono::duration<Rep, Period> d)
241 : {
242 8432 : return expires_after(std::chrono::duration_cast<duration>(d));
243 : }
244 :
245 : protected:
246 : explicit timer(handle h) noexcept : io_timer(std::move(h)) {}
247 :
248 : private:
249 : std::size_t do_cancel() override;
250 : std::size_t do_cancel_one();
251 : std::size_t do_update_expiry();
252 :
253 : /// Return the underlying implementation.
254 8508 : implementation& get() const noexcept
255 : {
256 8508 : return *static_cast<implementation*>(h_.get());
257 : }
258 : };
259 :
260 : } // namespace boost::corosio
261 :
262 : #endif
|