hierarchical_hash_wheel_timer/timers.rs
1use std::{fmt, hash::Hash, time::Duration};
2
3/// Indicate whether or not to reschedule a periodic timer
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum TimerReturn<S> {
6 /// Reschedule the timer
7 Reschedule(S),
8 /// Do not reschedule the timer
9 Cancel,
10}
11impl<S> TimerReturn<S> {
12 /// Whether or not this timer should be rescheduled
13 pub fn should_reschedule(&self) -> bool {
14 match self {
15 TimerReturn::Reschedule(_) => true,
16 TimerReturn::Cancel => false,
17 }
18 }
19
20 /// Replace the item stored in the `TimerReturn::Reschedule` variant
21 /// with the return value of the given function, given the current value
22 pub fn map<T, F>(self, f: F) -> TimerReturn<T>
23 where
24 F: FnOnce(S) -> T,
25 {
26 match self {
27 TimerReturn::Reschedule(s) => TimerReturn::Reschedule(f(s)),
28 TimerReturn::Cancel => TimerReturn::Cancel,
29 }
30 }
31}
32
33/// A trait for state that can be triggered once
34pub trait OneshotState {
35 /// The type of the unique id of the outstanding timeout
36 type Id: Hash + Clone + Eq;
37
38 /// A reference to the id associated with this state
39 fn id(&self) -> &Self::Id;
40 /// Trigger should be called by the timer implementation
41 /// when the timeout has expired.
42 ///
43 /// The method can be used for custom expiry actions,
44 /// but it is strongly recommended to keep these quick,
45 /// as long actions can delay the execution of later timers.
46 fn trigger(self);
47}
48
49/// A trait for state that can be triggered more than once once
50///
51/// This is different from oneshot timers,
52/// for the a similar reason to that why the `FnOnce` trait is different from the `Fn` trait.
53/// In effect, periodic state needs to be able to produce a new instance of itself for the next
54/// period, while oneshot state does not.
55pub trait PeriodicState {
56 /// The type of the unique id of the outstanding timeout
57 type Id: Hash + Clone + Eq;
58
59 /// A reference to the id associated with this state
60 fn id(&self) -> &Self::Id;
61
62 /// Trigger should be called by the timer implementation
63 /// when the timeout has expired.
64 ///
65 /// The method can be used for custom expiry actions,
66 /// but it is strongly recommended to keep these quick,
67 /// as long actions can delay the execution of later timers.
68 ///
69 /// For periodic actions the trigger actions may mutate (or replace)
70 /// the state of the timer entry itself.
71 /// Together with the ability to prevent reschedulling, this can be used
72 /// to implement "counter"-style timers, that happen a fixed number of times
73 /// before being dropped automatically.
74 fn trigger(self) -> TimerReturn<Self>
75 where
76 Self: Sized;
77}
78
79/// A concrete entry for an outstanding timeout
80#[derive(Debug)]
81pub enum TimerEntry<I, O, P>
82where
83 I: Hash + Clone + Eq,
84 O: OneshotState<Id = I>,
85 P: PeriodicState<Id = I>,
86{
87 /// A one-off timer
88 OneShot {
89 /// The length of the timeout
90 timeout: Duration,
91 /// The information to store along with the timer
92 state: O,
93 },
94 /// A recurring timer
95 Periodic {
96 /// The delay until the `action` is first invoked
97 delay: Duration,
98 /// The time between `action` invocations
99 period: Duration,
100 /// The information to store along with the timer
101 state: P,
102 },
103}
104
105impl<I, O, P> TimerEntry<I, O, P>
106where
107 I: Hash + Clone + Eq,
108 O: OneshotState<Id = I>,
109 P: PeriodicState<Id = I>,
110{
111 /// A reference to the id associated with this entry
112 ///
113 /// Equals calling the `id()` function on either state type.
114 pub fn id(&self) -> &I {
115 match self {
116 TimerEntry::OneShot { state, .. } => state.id(),
117 TimerEntry::Periodic { state, .. } => state.id(),
118 }
119 }
120
121 /// Returns the time until the timeout is supposed to be triggered
122 pub fn delay(&self) -> &Duration {
123 match self {
124 TimerEntry::OneShot { timeout, .. } => timeout,
125 TimerEntry::Periodic { delay, .. } => delay,
126 }
127 }
128}
129
130/// A basic low-level timer API
131///
132/// This allows behaviours to be scheduled for later execution.
133pub trait Timer {
134 /// A type to uniquely identify any timeout to be schedulled or cancelled
135 type Id: Hash + Clone + Eq;
136
137 /// The type of state to keep for oneshot timers
138 type OneshotState: OneshotState<Id = Self::Id>;
139
140 /// The type of state to keep for periodic timers
141 type PeriodicState: PeriodicState<Id = Self::Id>;
142
143 /// Schedule the `state` to be triggered once after the `timeout` expires
144 ///
145 /// # Note
146 ///
147 /// Depending on your system and the implementation used,
148 /// there is always a certain lag between the triggering of the `state`
149 /// and the `timeout` expiring on the system's clock.
150 /// Thus it is only guaranteed that the `state` is not triggered *before*
151 /// the `timeout` expires, but no bounds on the lag are given.
152 fn schedule_once(&mut self, timeout: Duration, state: Self::OneshotState);
153
154 /// Schedule the `state` to be triggered every `timeout` time units
155 ///
156 /// The first time, the `state` will be trigerreed after `delay` expires,
157 /// and then again every `timeout` time units after, unless the
158 /// [TimerReturn](TimerReturn) given specifies otherwise.
159 ///
160 /// # Note
161 ///
162 /// Depending on your system and the implementation used,
163 /// there is always a certain lag between the triggering of the `state`
164 /// and the `timeout` expiring on the system's clock.
165 /// Thus it is only guaranteed that the `state` is not triggered *before*
166 /// the `timeout` expires, but no bounds on the lag are given.
167 fn schedule_periodic(&mut self, delay: Duration, period: Duration, state: Self::PeriodicState);
168
169 /// Cancel the timer indicated by the unique `id`
170 fn cancel(&mut self, id: &Self::Id);
171}
172
173/// A timeout state for a one-shot timer using a closure as the triggering action
174pub struct OneShotClosureState<I> {
175 /// The id of the timeout state
176 id: I,
177 /// The action to invoke when the timeout expires
178 action: Box<dyn FnOnce(I) + Send + 'static>,
179}
180
181impl<I> OneShotClosureState<I> {
182 /// Produces a new instance of this state type
183 /// from a unique id and the action to be executed
184 /// when it expires.
185 pub fn new<F>(id: I, action: F) -> Self
186 where
187 F: FnOnce(I) + Send + 'static,
188 {
189 OneShotClosureState {
190 id,
191 action: Box::new(action),
192 }
193 }
194}
195
196#[cfg(feature = "uuid-extras")]
197impl OneShotClosureState<uuid::Uuid> {
198 /// Produces a new instance of this state type
199 /// using a random unique id and the action to be executed
200 /// when it expires.
201 ///
202 /// Uses `Uuid::new_v4()` internally.
203 pub fn with_random_id<F>(action: F) -> Self
204 where
205 F: FnOnce(uuid::Uuid) + Send + 'static,
206 {
207 Self::new(uuid::Uuid::new_v4(), action)
208 }
209}
210
211impl<I> OneshotState for OneShotClosureState<I>
212where
213 I: Hash + Clone + Eq,
214{
215 type Id = I;
216
217 fn id(&self) -> &Self::Id {
218 &self.id
219 }
220
221 fn trigger(self) {
222 (self.action)(self.id)
223 }
224}
225
226impl<I> fmt::Debug for OneShotClosureState<I>
227where
228 I: Hash + Clone + Eq + fmt::Debug,
229{
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(
232 f,
233 "OneShotClosureState(id={:?}, action=<function>)",
234 self.id
235 )
236 }
237}
238
239/// A timeout state for a periodic timer using a closure as the triggering action
240pub struct PeriodicClosureState<I> {
241 /// The id of the timeout state
242 id: I,
243 /// The action to invoke when the timeout expires
244 action: Box<dyn FnMut(I) -> TimerReturn<()> + Send + 'static>,
245}
246
247impl<I> PeriodicClosureState<I> {
248 /// Produces a new instance of this state type
249 /// from a unique id and the action to be executed
250 /// every time it expires.
251 pub fn new<F>(id: I, action: F) -> Self
252 where
253 F: FnMut(I) -> TimerReturn<()> + Send + 'static,
254 {
255 PeriodicClosureState {
256 id,
257 action: Box::new(action),
258 }
259 }
260}
261
262#[cfg(feature = "uuid-extras")]
263impl PeriodicClosureState<uuid::Uuid> {
264 /// Produces a new instance of this state type
265 /// using a random unique id and the action to be executed
266 /// every time it expires.
267 ///
268 /// Uses `Uuid::new_v4()` internally.
269 pub fn with_random_id<F>(action: F) -> Self
270 where
271 F: FnMut(uuid::Uuid) -> TimerReturn<()> + Send + 'static,
272 {
273 Self::new(uuid::Uuid::new_v4(), action)
274 }
275}
276
277impl<I> PeriodicState for PeriodicClosureState<I>
278where
279 I: Hash + Clone + Eq,
280{
281 type Id = I;
282
283 fn id(&self) -> &Self::Id {
284 &self.id
285 }
286
287 fn trigger(mut self) -> TimerReturn<Self>
288 where
289 Self: Sized,
290 {
291 let res = (self.action)(self.id.clone());
292 res.map(|_| self)
293 }
294}
295
296impl<I> fmt::Debug for PeriodicClosureState<I>
297where
298 I: Hash + Clone + Eq + fmt::Debug,
299{
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 write!(
302 f,
303 "PeriodicClosureState(id={:?}, action=<function>)",
304 self.id
305 )
306 }
307}
308
309/// This trait is a convenience API for [timers](Timer) that use the closure state types,
310/// i.e. [OneShotClosureState](OneShotClosureState) and [PeriodicClosureState](PeriodicClosureState).
311pub trait ClosureTimer: Timer {
312 /// Schedule the `action` to be executed once after the `timeout` expires
313 ///
314 /// # Note
315 ///
316 /// Depending on your system and the implementation used,
317 /// there is always a certain lag between the execution of the `action`
318 /// and the `timeout` expiring on the system's clock.
319 /// Thus it is only guaranteed that the `action` is not run *before*
320 /// the `timeout` expires, but no bounds on the lag are given.
321 fn schedule_action_once<F>(&mut self, id: Self::Id, timeout: Duration, action: F)
322 where
323 F: FnOnce(Self::Id) + Send + 'static;
324
325 /// Schedule the `action` to be run every `timeout` time units
326 ///
327 /// The first time, the `action` will be run after `delay` expires,
328 /// and then again every `timeout` time units after.
329 ///
330 /// # Note
331 ///
332 /// Depending on your system and the implementation used,
333 /// there is always a certain lag between the execution of the `action`
334 /// and the `timeout` expiring on the system's clock.
335 /// Thus it is only guaranteed that the `action` is not run *before*
336 /// the `timeout` expires, but no bounds on the lag are given.
337 fn schedule_action_periodic<F>(
338 &mut self,
339 id: Self::Id,
340 delay: Duration,
341 period: Duration,
342 action: F,
343 ) where
344 F: FnMut(Self::Id) -> TimerReturn<()> + Send + 'static;
345}
346
347impl<I, T> ClosureTimer for T
348where
349 I: Hash + Clone + Eq,
350 T: Timer<
351 Id = I,
352 OneshotState = OneShotClosureState<I>,
353 PeriodicState = PeriodicClosureState<I>,
354 >,
355{
356 fn schedule_action_once<F>(&mut self, id: Self::Id, timeout: Duration, action: F)
357 where
358 F: FnOnce(Self::Id) + Send + 'static,
359 {
360 self.schedule_once(timeout, OneShotClosureState::new(id, action))
361 }
362
363 fn schedule_action_periodic<F>(
364 &mut self,
365 id: Self::Id,
366 delay: Duration,
367 period: Duration,
368 action: F,
369 ) where
370 F: FnMut(Self::Id) -> TimerReturn<()> + Send + 'static,
371 {
372 self.schedule_periodic(delay, period, PeriodicClosureState::new(id, action))
373 }
374}