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}