1
//! Types and code to track the readiness status of a directory cache.
2

            
3
use std::time::{Duration, Instant};
4
use tor_basic_utils::retry::RetryDelay;
5

            
6
/// Status information about whether a
7
/// [`FallbackDir`](crate::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)]
13
pub(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

            
23
impl DirStatus {
24
    /// Construct a new DirStatus object with a given lower-bound for delays
25
    /// after failure.
26
144648
    pub(crate) fn new(delay_floor: Duration) -> Self {
27
144648
        DirStatus {
28
144648
            delay: RetryDelay::from_duration(delay_floor),
29
144648
            retry_at: None,
30
144648
        }
31
144648
    }
32

            
33
    /// Return true if this `Status` is usable at the time `now`.
34
22226
    pub(crate) fn usable_at(&self, now: Instant) -> bool {
35
22226
        match self.retry_at {
36
236
            Some(ready) => now >= ready,
37
21990
            None => true,
38
        }
39
22226
    }
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
56
    pub(crate) fn next_retriable(&self) -> Option<Instant> {
46
56
        self.retry_at
47
56
    }
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
14
    pub(crate) fn note_success(&mut self) {
54
14
        self.retry_at = None;
55
14
        self.delay.reset();
56
14
    }
57

            
58
    /// Record that the associated fallback directory has failed.
59
28
    pub(crate) fn note_failure(&mut self, now: Instant) {
60
28
        let mut rng = rand::rng();
61
28
        self.retry_at = Some(now + self.delay.next_delay(&mut rng));
62
28
    }
63
}
64

            
65
#[cfg(test)]
66
mod 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_duration_subtraction)]
77
    #![allow(clippy::useless_vec)]
78
    #![allow(clippy::needless_pass_by_value)]
79
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
80
    use super::*;
81

            
82
    #[test]
83
    fn status_basics() {
84
        let now = Instant::now();
85

            
86
        /// floor to use for testing.
87
        const FLOOR: Duration = Duration::from_secs(99);
88

            
89
        let mut status = DirStatus::new(FLOOR);
90
        // newly created status is usable.
91
        assert!(status.usable_at(now));
92

            
93
        // no longer usable after a failure.
94
        status.note_failure(now);
95
        assert_eq!(status.next_retriable().unwrap(), now + FLOOR);
96
        assert!(!status.usable_at(now));
97

            
98
        // Not enough time has passed.
99
        assert!(!status.usable_at(now + FLOOR / 2));
100

            
101
        // Enough time has passed.
102
        assert!(status.usable_at(now + FLOOR));
103

            
104
        // Mark as failed again; the timeout will (probably) be longer.
105
        status.note_failure(now + FLOOR);
106
        assert!(status.next_retriable().unwrap() >= now + FLOOR * 2);
107
        assert!(!status.usable_at(now + FLOOR));
108

            
109
        // Mark as succeeded; it should be usable immediately.
110
        status.note_success();
111
        assert!(status.usable_at(now + FLOOR));
112
    }
113
}