hotshot_libp2p_networking/network/behaviours/
exponential_backoff.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7use std::time::{Duration, Instant};
8
9/// Track (with exponential backoff)
10/// sending of some sort of message
11#[derive(Debug, Clone, Eq, Hash, PartialEq)]
12pub struct ExponentialBackoff {
13    /// Value to reset to when reset is called
14    reset_val: Duration,
15    /// factor to back off by
16    backoff_factor: u32,
17    /// the current timeout amount
18    timeout: Duration,
19    /// when we started the timeout
20    started: Option<Instant>,
21}
22
23impl ExponentialBackoff {
24    /// Create new backoff
25    #[must_use]
26    pub fn new(backoff_factor: u32, next_timeout: Duration) -> Self {
27        ExponentialBackoff {
28            backoff_factor,
29            timeout: next_timeout * backoff_factor,
30            reset_val: next_timeout,
31            started: None,
32        }
33    }
34
35    /// reset backoff
36    pub fn reset(&mut self) {
37        self.timeout = self.reset_val;
38    }
39
40    /// start next timeout
41    /// result: whether or not we succeeded
42    /// if we succeeded, reset the timeout
43    /// else increment the timeout by a factor
44    /// of `timeout`
45    pub fn start_next(&mut self, result: bool) {
46        // success
47        if result {
48            self.timeout = self.reset_val;
49            self.started = Some(Instant::now());
50        }
51        // failure
52        else {
53            // note we want to prevent overflow.
54            if let Some(r) = self.timeout.checked_mul(self.backoff_factor) {
55                self.timeout = r;
56            }
57            self.started = Some(Instant::now());
58        }
59    }
60
61    /// Return the timeout duration and start the next timeout.
62    pub fn next_timeout(&mut self, result: bool) -> Duration {
63        let timeout = self.timeout;
64        self.start_next(result);
65        timeout
66    }
67    /// Whether or not the timeout is expired
68    #[must_use]
69    pub fn is_expired(&self) -> bool {
70        if let Some(then) = self.started {
71            then.elapsed() > self.timeout
72        } else {
73            true
74        }
75    }
76    /// Marked as expired regardless of time left.
77    pub fn expire(&mut self) {
78        self.started = None;
79    }
80}
81
82impl Default for ExponentialBackoff {
83    fn default() -> Self {
84        Self {
85            reset_val: Duration::from_millis(500),
86            backoff_factor: 2,
87            timeout: Duration::from_millis(500),
88            started: None,
89        }
90    }
91}