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
481
    pub(crate) const fn new() -> Self {
26
481
        AtomicOptTimestamp {
27
481
            latest: AtomicU64::new(0),
28
481
        }
29
481
    }
30

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

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

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

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

            
53
    /// Return the time since `update` was last called.
54
    ///
55
    /// Return `None` if update was never called.
56
403482
    pub(crate) fn time_since_update(&self) -> Option<coarsetime::Duration> {
57
403482
        self.time_since_update_at(coarsetime::Instant::now())
58
403482
    }
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
403506
    pub(crate) fn time_since_update_at(
67
403506
        &self,
68
403506
        now: coarsetime::Instant,
69
403506
    ) -> Option<coarsetime::Duration> {
70
403506
        let earlier = self.latest.load(Ordering::Relaxed);
71
403506
        let now = now.as_ticks();
72
403506
        if now >= earlier && earlier != 0 {
73
175
            Some(coarsetime::Duration::from_ticks(now - earlier))
74
        } else {
75
403331
            None
76
        }
77
403506
    }
78

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

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

            
103
    use super::*;
104

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

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

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

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

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

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

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

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

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

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

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

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