1
//! Implement a fast 'timestamp' for determining when an event last
2
//! happened.
3

            
4
use std::sync::atomic::{AtomicU64, Ordering};
5

            
6
/// An object for determining whether an event happened,
7
/// and if yes, when it happened.
8
///
9
/// Every `Timestamp` has internal mutability.  A timestamp can move
10
/// forward in time, but never backwards.
11
///
12
/// Internally, it uses the `coarsetime` crate to represent times in a way
13
/// that lets us do atomic updates.
14
#[derive(Default, Debug)]
15
pub(crate) struct OptTimestamp {
16
    /// A timestamp (from `coarsetime`) describing when this timestamp
17
    /// was last updated.
18
    ///
19
    /// I'd rather just use [`coarsetime::Instant`], but that doesn't have
20
    /// an atomic form.
21
    latest: AtomicU64,
22
}
23
impl OptTimestamp {
24
    /// Construct a new timestamp that has never been updated.
25
96
    pub(crate) const fn new() -> Self {
26
96
        OptTimestamp {
27
96
            latest: AtomicU64::new(0),
28
96
        }
29
96
    }
30

            
31
    /// Update this timestamp to (at least) the current time.
32
255
    pub(crate) fn update(&self) {
33
255
        // TODO: Do we want to use 'Instant::recent() instead,' and
34
255
        // add an updater thread?
35
255
        self.update_to(coarsetime::Instant::now());
36
255
    }
37

            
38
    /// If the timestamp is currently unset, then set it to the current time.
39
    /// Otherwise leave it alone.
40
22
    pub(crate) fn update_if_none(&self) {
41
22
        let now = coarsetime::Instant::now().as_ticks();
42
22

            
43
22
        let _ignore = self
44
22
            .latest
45
22
            .compare_exchange(0, now, Ordering::Relaxed, Ordering::Relaxed);
46
22
    }
47

            
48
    /// Clear the timestamp and make it not updated again.
49
5
    pub(crate) fn clear(&self) {
50
5
        self.latest.store(0, Ordering::Relaxed);
51
5
    }
52

            
53
    /// Return the time since `update` was last called.
54
    ///
55
    /// Return `None` if update was never called.
56
5372
    pub(crate) fn time_since_update(&self) -> Option<coarsetime::Duration> {
57
5372
        self.time_since_update_at(coarsetime::Instant::now())
58
5372
    }
59

            
60
    /// Return the time between the time when `udpate` was last
61
    /// called, and the time `now`.
62
    ///
63
    /// Return `None` if `update` was never called, or `now` is before
64
    /// that time.
65
    #[inline]
66
5384
    pub(crate) fn time_since_update_at(
67
5384
        &self,
68
5384
        now: coarsetime::Instant,
69
5384
    ) -> Option<coarsetime::Duration> {
70
5384
        let earlier = self.latest.load(Ordering::Relaxed);
71
5384
        let now = now.as_ticks();
72
5384
        if now >= earlier && earlier != 0 {
73
17
            Some(coarsetime::Duration::from_ticks(now - earlier))
74
        } else {
75
5367
            None
76
        }
77
5384
    }
78

            
79
    /// Update this timestamp to (at least) the time `now`.
80
    #[inline]
81
259
    pub(crate) fn update_to(&self, now: coarsetime::Instant) {
82
259
        self.latest.fetch_max(now.as_ticks(), Ordering::Relaxed);
83
259
    }
84
}
85

            
86
#[cfg(test)]
87
#[allow(clippy::unwrap_used)]
88
mod test {
89

            
90
    use super::*;
91

            
92
    #[test]
93
    fn opt_timestamp() {
94
        use coarsetime::{Duration, Instant};
95

            
96
        let ts = OptTimestamp::new();
97
        assert!(ts.time_since_update().is_none());
98

            
99
        let zero = Duration::from_secs(0);
100
        let one_sec = Duration::from_secs(1);
101

            
102
        let first = Instant::now();
103
        let in_a_bit = first + one_sec * 10;
104
        let even_later = first + one_sec * 25;
105

            
106
        assert!(ts.time_since_update_at(first).is_none());
107

            
108
        ts.update_to(first);
109
        assert_eq!(ts.time_since_update_at(first), Some(zero));
110
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(one_sec * 10));
111

            
112
        ts.update_to(in_a_bit);
113
        assert!(ts.time_since_update_at(first).is_none());
114
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(zero));
115
        assert_eq!(ts.time_since_update_at(even_later), Some(one_sec * 15));
116

            
117
        // Make sure we can't move backwards.
118
        ts.update_to(first);
119
        assert!(ts.time_since_update_at(first).is_none());
120
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(zero));
121
        assert_eq!(ts.time_since_update_at(even_later), Some(one_sec * 15));
122

            
123
        ts.clear();
124
        assert!(ts.time_since_update_at(first).is_none());
125
        assert!(ts.time_since_update_at(in_a_bit).is_none());
126
        assert!(ts.time_since_update_at(even_later).is_none());
127
    }
128

            
129
    #[test]
130
    fn update_if_none() {
131
        let ts = OptTimestamp::new();
132
        assert!(ts.time_since_update().is_none());
133

            
134
        // Calling "update_if_none" on a None OptTimestamp should set it.
135
        let time1 = coarsetime::Instant::now();
136
        ts.update_if_none();
137
        let d = ts.time_since_update();
138
        let time2 = coarsetime::Instant::now();
139
        assert!(d.is_some());
140
        assert!(d.unwrap() <= time2 - time1);
141

            
142
        std::thread::sleep(std::time::Duration::from_millis(100));
143
        // Calling "update_if_none" on a Some OptTimestamp doesn't change it.
144
        let time3 = coarsetime::Instant::now();
145
        // If coarsetime doesn't register this, then the rest of our test won't work.
146
        assert!(time3 > time2);
147
        ts.update_if_none();
148
        let d2 = ts.time_since_update();
149
        assert!(d2.is_some());
150
        assert!(d2.unwrap() > d.unwrap());
151
    }
152
}