1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::collections::VecDeque;
use std::time::{Duration, Instant};

#[derive(Debug, Clone, Copy)]
pub struct RestartIntensity<D> {
    pub max_restarts: usize,
    pub within: D,
}

#[derive(Debug, Clone)]
pub struct RestartStats<I>(VecDeque<I>);

#[derive(Debug, thiserror::Error)]
#[error("Max restart intensity reached")]
pub struct MaxRestartIntensityReached;

impl<D> RestartIntensity<D> {
    pub fn new(max_restarts: usize, within: D) -> Self {
        Self { max_restarts, within }
    }

    pub fn new_stats(&self) -> RestartStats<<D as DurationToInstant>::Instant>
    where
        D: DurationToInstant,
    {
        RestartStats::new()
    }

    pub fn report_exit<I>(
        &self,
        stats: &mut RestartStats<I>,
        now: I,
    ) -> Result<(), MaxRestartIntensityReached>
    where
        I: ElapsedSince<Elapsed = D>,
    {
        stats.truncate(&now, &self.within).push(now);
        if stats.len() > self.max_restarts {
            Err(MaxRestartIntensityReached)
        } else {
            Ok(())
        }
    }
}

impl<I> RestartStats<I> {
    fn new() -> Self {
        Self(Default::default())
    }
}

impl<I> RestartStats<I> {
    fn len(&self) -> usize {
        self.0.len()
    }
    fn truncate(&mut self, now: &I, within: &<I as ElapsedSince>::Elapsed) -> &mut Self
    where
        I: ElapsedSince,
    {
        while self.0.front().into_iter().all(|past| now.elapsed_since(past) > *within) {
            if self.0.pop_front().is_none() {
                break
            };
        }
        self
    }

    fn push(&mut self, now: I) -> &mut Self
    where
        I: Ord,
    {
        if self.0.back().into_iter().all(|past| *past < now) {
            self.0.push_back(now);
            self
        } else {
            panic!("attempt to insert a value that comes earlier than the last in the queue")
        }
    }
}

pub trait DurationToInstant: Clone {
    type Instant: Clone;
}

pub trait ElapsedSince: Ord + Clone {
    type Elapsed: Ord + Clone;
    fn elapsed_since(&self, past: &Self) -> Self::Elapsed;
}

impl Default for RestartIntensity<Duration> {
    fn default() -> Self {
        Self { max_restarts: 1, within: Duration::from_secs(5) }
    }
}

impl DurationToInstant for usize {
    type Instant = usize;
}
impl DurationToInstant for Duration {
    type Instant = Instant;
}

impl ElapsedSince for usize {
    type Elapsed = usize;
    fn elapsed_since(&self, past: &Self) -> Self::Elapsed {
        self.checked_sub(*past).expect("`past` is greater than `self`")
    }
}

impl ElapsedSince for Instant {
    type Elapsed = Duration;
    fn elapsed_since(&self, past: &Self) -> Self::Elapsed {
        self.duration_since(*past)
    }
}

#[test]
fn basic_test_usizes() {
    let intensity = RestartIntensity::new(3, 10);
    let mut stats = intensity.new_stats();

    assert!(intensity.report_exit(&mut stats, 1).is_ok());
    assert!(intensity.report_exit(&mut stats, 2).is_ok());
    assert!(intensity.report_exit(&mut stats, 3).is_ok());
    assert!(intensity.report_exit(&mut stats, 4).is_err());
}

#[test]
fn basic_test_instant_and_duration() {
    let intensity = RestartIntensity::new(3, Duration::from_secs(10));
    let mut stats = intensity.new_stats();

    std::thread::sleep(Duration::from_secs(1));
    assert!(intensity.report_exit(&mut stats, Instant::now()).is_ok());
    std::thread::sleep(Duration::from_secs(1));
    assert!(intensity.report_exit(&mut stats, Instant::now()).is_ok());
    std::thread::sleep(Duration::from_secs(1));
    assert!(intensity.report_exit(&mut stats, Instant::now()).is_ok());
    std::thread::sleep(Duration::from_secs(1));
    assert!(intensity.report_exit(&mut stats, Instant::now()).is_err());
}