Skip to main content

tor_guardmgr/
dirstatus.rs

1//! Types and code to track the readiness status of a directory cache.
2
3use tor_basic_utils::retry::RetryDelay;
4use web_time_compat::{Duration, Instant};
5
6/// Status information about whether a
7/// [`FallbackDir`](tor_dircommon::fallback::FallbackDir) or
8/// [`Guard`](crate::guard::Guard) is currently usable as a directory cache.
9///
10/// This structure is used to track whether the cache has recently failed, and
11/// if so, when it can be retried.
12#[derive(Debug, Clone)]
13pub(crate) struct DirStatus {
14    /// Used to decide how long to delay before retrying a fallback cache
15    /// that has failed.
16    delay: RetryDelay,
17    /// A time before which we should assume that this fallback cache is broken.
18    ///
19    /// If None, then this fallback cache is ready to use right away.
20    retry_at: Option<Instant>,
21}
22
23impl DirStatus {
24    /// Construct a new DirStatus object with a given lower-bound for delays
25    /// after failure.
26    pub(crate) fn new(delay_floor: Duration) -> Self {
27        DirStatus {
28            delay: RetryDelay::from_duration(delay_floor),
29            retry_at: None,
30        }
31    }
32
33    /// Return true if this `Status` is usable at the time `now`.
34    pub(crate) fn usable_at(&self, now: Instant) -> bool {
35        match self.retry_at {
36            Some(ready) => now >= ready,
37            None => true,
38        }
39    }
40
41    /// Return the time at which this `Status` can next be retried.
42    ///
43    /// A return value of `None`, or of a time in the past, indicates that this
44    /// status can be used immediately.
45    pub(crate) fn next_retriable(&self) -> Option<Instant> {
46        self.retry_at
47    }
48
49    /// Record that the associated fallback directory has been used successfully.
50    ///
51    /// This should only be done after successfully handling a whole reply from the
52    /// directory.
53    pub(crate) fn note_success(&mut self) {
54        self.retry_at = None;
55        self.delay.reset();
56    }
57
58    /// Record that the associated fallback directory has failed.
59    pub(crate) fn note_failure(&mut self, now: Instant) {
60        let mut rng = rand::rng();
61        self.retry_at = Some(now + self.delay.next_delay(&mut rng));
62    }
63}
64
65#[cfg(test)]
66mod test {
67    // @@ begin test lint list maintained by maint/add_warning @@
68    #![allow(clippy::bool_assert_comparison)]
69    #![allow(clippy::clone_on_copy)]
70    #![allow(clippy::dbg_macro)]
71    #![allow(clippy::mixed_attributes_style)]
72    #![allow(clippy::print_stderr)]
73    #![allow(clippy::print_stdout)]
74    #![allow(clippy::single_char_pattern)]
75    #![allow(clippy::unwrap_used)]
76    #![allow(clippy::unchecked_time_subtraction)]
77    #![allow(clippy::useless_vec)]
78    #![allow(clippy::needless_pass_by_value)]
79    #![allow(clippy::string_slice)] // See arti#2571
80    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
81    use super::*;
82    use web_time_compat::InstantExt;
83
84    #[test]
85    fn status_basics() {
86        let now = Instant::get();
87
88        /// floor to use for testing.
89        const FLOOR: Duration = Duration::from_secs(99);
90
91        let mut status = DirStatus::new(FLOOR);
92        // newly created status is usable.
93        assert!(status.usable_at(now));
94
95        // no longer usable after a failure.
96        status.note_failure(now);
97        assert_eq!(status.next_retriable().unwrap(), now + FLOOR);
98        assert!(!status.usable_at(now));
99
100        // Not enough time has passed.
101        assert!(!status.usable_at(now + FLOOR / 2));
102
103        // Enough time has passed.
104        assert!(status.usable_at(now + FLOOR));
105
106        // Mark as failed again; the timeout will (probably) be longer.
107        status.note_failure(now + FLOOR);
108        assert!(status.next_retriable().unwrap() >= now + FLOOR * 2);
109        assert!(!status.usable_at(now + FLOOR));
110
111        // Mark as succeeded; it should be usable immediately.
112        status.note_success();
113        assert!(status.usable_at(now + FLOOR));
114    }
115}