1
//! Configure timers for a timer for retrying a single failed fetch or object.
2
//!
3
//! For a more information on the algorithm, see
4
//! [`RetryDelay`].
5

            
6
use std::num::{NonZeroU8, NonZeroU32};
7
use std::time::Duration;
8

            
9
use derive_deftly::Deftly;
10
use tor_basic_utils::retry::RetryDelay;
11
use tor_config::ConfigBuildError;
12
use tor_config::derive::prelude::*;
13

            
14
/// Configuration for how many times to retry a download, with what
15
/// frequency.
16
#[derive(Debug, Deftly, Copy, Clone, Eq, PartialEq)]
17
#[derive_deftly(TorConfig)]
18
pub struct DownloadSchedule {
19
    /// How many attempts to make before giving up?
20
    #[deftly(tor_config(default = r#"NonZeroU32::new(3).expect("Somehow 3==0")"#))]
21
    attempts: NonZeroU32,
22

            
23
    /// The amount of time to delay after the first failure, and a
24
    /// lower-bound for future delays.
25
    #[deftly(tor_config(default = "Duration::from_millis(1000)"))]
26
    initial_delay: Duration,
27

            
28
    /// When we want to download a bunch of these at a time, how many
29
    /// attempts should we try to launch at once?
30
    #[deftly(tor_config(default = r#"NonZeroU8::new(1).expect("Somehow 1==0")"#))]
31
    parallelism: NonZeroU8,
32
}
33

            
34
impl DownloadScheduleBuilder {
35
    /// Default value for retry_bootstrap in DownloadScheduleConfig.
36
6553
    pub fn build_retry_bootstrap(&self) -> Result<DownloadSchedule, ConfigBuildError> {
37
6553
        let mut bld = self.clone();
38
6553
        bld.attempts.get_or_insert(128);
39
8130
        bld.initial_delay.get_or_insert_with(|| Duration::new(1, 0));
40
6553
        bld.parallelism.get_or_insert(1);
41
6553
        bld.build()
42
6553
    }
43

            
44
    /// Default value for microdesc_bootstrap in DownloadScheduleConfig.
45
6553
    pub fn build_retry_microdescs(&self) -> Result<DownloadSchedule, ConfigBuildError> {
46
6553
        let mut bld = self.clone();
47
6553
        bld.attempts.get_or_insert(3);
48
8121
        bld.initial_delay.get_or_insert_with(|| Duration::new(1, 0));
49
6553
        bld.parallelism.get_or_insert(4);
50
6553
        bld.build()
51
6553
    }
52
}
53

            
54
impl DownloadSchedule {
55
    /// Return an iterator to use over all the supported attempts for
56
    /// this configuration.
57
39
    pub fn attempts(&self) -> impl Iterator<Item = u32> + use<> {
58
39
        0..(self.attempts.into())
59
39
    }
60

            
61
    /// Return the number of times that we're supposed to retry, according
62
    /// to this DownloadSchedule.
63
2
    pub fn n_attempts(&self) -> u32 {
64
2
        self.attempts.into()
65
2
    }
66

            
67
    /// Return the number of parallel attempts that we're supposed to launch,
68
    /// according to this DownloadSchedule.
69
148
    pub fn parallelism(&self) -> u8 {
70
148
        self.parallelism.into()
71
148
    }
72

            
73
    /// Return a RetryDelay object for this configuration.
74
    ///
75
    /// If the initial delay is longer than 32
76
39
    pub fn schedule(&self) -> RetryDelay {
77
39
        RetryDelay::from_duration(self.initial_delay)
78
39
    }
79
}
80

            
81
#[cfg(test)]
82
mod test {
83
    // @@ begin test lint list maintained by maint/add_warning @@
84
    #![allow(clippy::bool_assert_comparison)]
85
    #![allow(clippy::clone_on_copy)]
86
    #![allow(clippy::dbg_macro)]
87
    #![allow(clippy::mixed_attributes_style)]
88
    #![allow(clippy::print_stderr)]
89
    #![allow(clippy::print_stdout)]
90
    #![allow(clippy::single_char_pattern)]
91
    #![allow(clippy::unwrap_used)]
92
    #![allow(clippy::unchecked_time_subtraction)]
93
    #![allow(clippy::useless_vec)]
94
    #![allow(clippy::needless_pass_by_value)]
95
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
96
    use super::*;
97
    use tor_basic_utils::test_rng::testing_rng;
98

            
99
    #[test]
100
    fn config() {
101
        // default configuration is 3 tries, 1000 msec initial delay
102
        let cfg = DownloadSchedule::default();
103
        let one_sec = Duration::from_secs(1);
104
        let mut rng = testing_rng();
105

            
106
        assert_eq!(cfg.n_attempts(), 3);
107
        let v: Vec<_> = cfg.attempts().collect();
108
        assert_eq!(&v[..], &[0, 1, 2]);
109

            
110
        assert_eq!(cfg.initial_delay, one_sec);
111
        let mut sched = cfg.schedule();
112
        assert_eq!(sched.next_delay(&mut rng), one_sec);
113

            
114
        // Try schedules with zeroes and show that they fail
115
        DownloadSchedule::builder()
116
            .attempts(0)
117
            .build()
118
            .expect_err("built with 0 retries");
119
        DownloadSchedule::builder()
120
            .parallelism(0)
121
            .build()
122
            .expect_err("built with 0 parallelism");
123
    }
124
}