Skip to main content

ash_flare/
restart.rs

1//! Restart policies and strategies for supervision
2
3use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5use std::time::{Duration, Instant};
6
7use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
8
9/// Restart strategy for supervisor children
10#[derive(
11    Debug,
12    Default,
13    Clone,
14    Copy,
15    PartialEq,
16    Eq,
17    Serialize,
18    Deserialize,
19    Archive,
20    RkyvSerialize,
21    RkyvDeserialize,
22)]
23#[rkyv(derive(Debug))]
24pub enum RestartStrategy {
25    /// Restart only the failed child (`:one_for_one`)
26    #[default]
27    OneForOne,
28    /// Restart all children if any child fails (`:one_for_all`)
29    OneForAll,
30    /// Restart failed child and all children started after it (`:rest_for_one`)
31    RestForOne,
32}
33
34/// When to restart a child
35#[derive(
36    Debug,
37    Default,
38    Clone,
39    Copy,
40    PartialEq,
41    Eq,
42    Serialize,
43    Deserialize,
44    Archive,
45    RkyvSerialize,
46    RkyvDeserialize,
47)]
48#[rkyv(derive(Debug))]
49pub enum RestartPolicy {
50    /// Always restart when child terminates (`:permanent`)
51    #[default]
52    Permanent,
53    /// Never restart (`:temporary`)
54    Temporary,
55    /// Restart only if abnormal termination (`:transient`)
56    Transient,
57}
58
59/// Restart intensity limits with max restarts within a time window
60#[derive(Debug, Clone, Copy)]
61pub struct RestartIntensity {
62    /// Maximum number of restarts allowed
63    pub max_restarts: usize,
64    /// Within this time period (in seconds)
65    pub within_seconds: u64,
66}
67
68impl RestartIntensity {
69    /// Creates a new RestartIntensity with the specified limits.
70    ///
71    /// # Examples
72    /// ```
73    /// use ash_flare::RestartIntensity;
74    /// let intensity = RestartIntensity::new(5, 10);
75    /// assert_eq!(intensity.max_restarts, 5);
76    /// assert_eq!(intensity.within_seconds, 10);
77    /// ```
78    #[inline]
79    pub const fn new(max_restarts: usize, within_seconds: u64) -> Self {
80        Self {
81            max_restarts,
82            within_seconds,
83        }
84    }
85}
86
87impl Default for RestartIntensity {
88    fn default() -> Self {
89        Self::new(3, 5)
90    }
91}
92
93/// Tracks restart history for intensity monitoring using a sliding time window
94#[derive(Debug)]
95pub(crate) struct RestartTracker {
96    intensity: RestartIntensity,
97    restart_times: VecDeque<Instant>,
98}
99
100impl RestartTracker {
101    pub(crate) fn new(intensity: RestartIntensity) -> Self {
102        Self {
103            intensity,
104            // Pre-allocate with max_restarts + 1 to avoid reallocations
105            restart_times: VecDeque::with_capacity(intensity.max_restarts + 1),
106        }
107    }
108
109    /// Records a restart and returns true if intensity limit exceeded
110    pub(crate) fn record_restart(&mut self) -> bool {
111        let now = Instant::now();
112        let cutoff = now - Duration::from_secs(self.intensity.within_seconds);
113
114        // Remove old restarts outside the time window
115        while let Some(&time) = self.restart_times.front() {
116            if time < cutoff {
117                self.restart_times.pop_front();
118            } else {
119                break;
120            }
121        }
122
123        self.restart_times.push_back(now);
124
125        // Check if we've exceeded the limit
126        self.restart_times.len() > self.intensity.max_restarts
127    }
128
129    #[allow(dead_code)]
130    pub(crate) fn reset(&mut self) {
131        self.restart_times.clear();
132    }
133}