tor_proto/util/
ts.rs

1//! Implement a fast 'timestamp' for determining when an event last
2//! happened.
3
4use 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)]
15pub(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}
23impl AtomicOptTimestamp {
24    /// Construct a new timestamp that has never been updated.
25    pub(crate) const fn new() -> Self {
26        AtomicOptTimestamp {
27            latest: AtomicU64::new(0),
28        }
29    }
30
31    /// Update this timestamp to (at least) the current time.
32    pub(crate) fn update(&self) {
33        // TODO: Do we want to use 'Instant::recent() instead,' and
34        // add an updater thread?
35        self.update_to(coarsetime::Instant::now());
36    }
37
38    /// If the timestamp is currently unset, then set it to the current time.
39    /// Otherwise leave it alone.
40    pub(crate) fn update_if_none(&self) {
41        let now = coarsetime::Instant::now().as_ticks();
42
43        let _ignore = self
44            .latest
45            .compare_exchange(0, now, Ordering::Relaxed, Ordering::Relaxed);
46    }
47
48    /// Clear the timestamp and make it not updated again.
49    pub(crate) fn clear(&self) {
50        self.latest.store(0, Ordering::Relaxed);
51    }
52
53    /// Return the time since `update` was last called.
54    ///
55    /// Return `None` if update was never called.
56    pub(crate) fn time_since_update(&self) -> Option<coarsetime::Duration> {
57        self.time_since_update_at(coarsetime::Instant::now())
58    }
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    pub(crate) fn time_since_update_at(
67        &self,
68        now: coarsetime::Instant,
69    ) -> Option<coarsetime::Duration> {
70        let earlier = self.latest.load(Ordering::Relaxed);
71        let now = now.as_ticks();
72        if now >= earlier && earlier != 0 {
73            Some(coarsetime::Duration::from_ticks(now - earlier))
74        } else {
75            None
76        }
77    }
78
79    /// Update this timestamp to (at least) the time `now`.
80    #[inline]
81    pub(crate) fn update_to(&self, now: coarsetime::Instant) {
82        self.latest.fetch_max(now.as_ticks(), Ordering::Relaxed);
83    }
84}
85
86#[cfg(test)]
87mod 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}