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)]
87#[allow(clippy::unwrap_used)]
88mod 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}