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 AtomicOptTimestamp {
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 AtomicOptTimestamp {
24
    /// Construct a new timestamp that has never been updated.
25
726
    pub(crate) const fn new() -> Self {
26
726
        AtomicOptTimestamp {
27
726
            latest: AtomicU64::new(0),
28
726
        }
29
726
    }
30

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

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

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

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

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

            
60
    /// Return the time between the time when `update` 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
586110
    pub(crate) fn time_since_update_at(
67
586110
        &self,
68
586110
        now: coarsetime::Instant,
69
586110
    ) -> Option<coarsetime::Duration> {
70
586110
        let earlier = self.latest.load(Ordering::Relaxed);
71
586110
        let now = now.as_ticks();
72
586110
        if now >= earlier && earlier != 0 {
73
180
            Some(coarsetime::Duration::from_ticks(now - earlier))
74
        } else {
75
585930
            None
76
        }
77
586110
    }
78

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

            
86
#[cfg(test)]
87
mod test {
88
    // @@ begin test lint list maintained by maint/add_warning @@
89
    #![allow(clippy::bool_assert_comparison)]
90
    #![allow(clippy::clone_on_copy)]
91
    #![allow(clippy::dbg_macro)]
92
    #![allow(clippy::mixed_attributes_style)]
93
    #![allow(clippy::print_stderr)]
94
    #![allow(clippy::print_stdout)]
95
    #![allow(clippy::single_char_pattern)]
96
    #![allow(clippy::unwrap_used)]
97
    #![allow(clippy::unchecked_duration_subtraction)]
98
    #![allow(clippy::useless_vec)]
99
    #![allow(clippy::needless_pass_by_value)]
100
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
101

            
102
    use super::*;
103

            
104
    #[test]
105
    fn opt_timestamp() {
106
        use coarsetime::{Duration, Instant};
107

            
108
        let ts = AtomicOptTimestamp::new();
109
        assert!(ts.time_since_update().is_none());
110

            
111
        let zero = Duration::from_secs(0);
112
        let one_sec = Duration::from_secs(1);
113

            
114
        let first = Instant::now();
115
        let in_a_bit = first + one_sec * 10;
116
        let even_later = first + one_sec * 25;
117

            
118
        assert!(ts.time_since_update_at(first).is_none());
119

            
120
        ts.update_to(first);
121
        assert_eq!(ts.time_since_update_at(first), Some(zero));
122
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(one_sec * 10));
123

            
124
        ts.update_to(in_a_bit);
125
        assert!(ts.time_since_update_at(first).is_none());
126
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(zero));
127
        assert_eq!(ts.time_since_update_at(even_later), Some(one_sec * 15));
128

            
129
        // Make sure we can't move backwards.
130
        ts.update_to(first);
131
        assert!(ts.time_since_update_at(first).is_none());
132
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(zero));
133
        assert_eq!(ts.time_since_update_at(even_later), Some(one_sec * 15));
134

            
135
        ts.clear();
136
        assert!(ts.time_since_update_at(first).is_none());
137
        assert!(ts.time_since_update_at(in_a_bit).is_none());
138
        assert!(ts.time_since_update_at(even_later).is_none());
139
    }
140

            
141
    #[test]
142
    fn update_if_none() {
143
        let ts = AtomicOptTimestamp::new();
144
        assert!(ts.time_since_update().is_none());
145

            
146
        // Calling "update_if_none" on a None AtomicOptTimestamp should set it.
147
        let time1 = coarsetime::Instant::now();
148
        ts.update_if_none();
149
        let d = ts.time_since_update();
150
        let time2 = coarsetime::Instant::now();
151
        assert!(d.is_some());
152
        assert!(d.unwrap() <= time2 - time1);
153

            
154
        std::thread::sleep(std::time::Duration::from_millis(100));
155
        // Calling "update_if_none" on a Some AtomicOptTimestamp doesn't change it.
156
        let time3 = coarsetime::Instant::now();
157
        // If coarsetime doesn't register this, then the rest of our test won't work.
158
        assert!(time3 > time2);
159
        ts.update_if_none();
160
        let d2 = ts.time_since_update();
161
        assert!(d2.is_some());
162
        assert!(d2.unwrap() > d.unwrap());
163
    }
164
}