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    #[must_use]
80    pub const fn new(max_restarts: usize, within_seconds: u64) -> Self {
81        Self {
82            max_restarts,
83            within_seconds,
84        }
85    }
86}
87
88impl Default for RestartIntensity {
89    fn default() -> Self {
90        Self::new(3, 5)
91    }
92}
93
94/// Tracks restart history for intensity monitoring using a sliding time window
95#[derive(Debug)]
96pub(crate) struct RestartTracker {
97    intensity: RestartIntensity,
98    restart_times: VecDeque<Instant>,
99}
100
101impl RestartTracker {
102    pub(crate) fn new(intensity: RestartIntensity) -> Self {
103        Self {
104            intensity,
105            // Pre-allocate with max_restarts + 1 to avoid reallocations
106            restart_times: VecDeque::with_capacity(intensity.max_restarts.saturating_add(1)),
107        }
108    }
109
110    /// Records a restart and returns true if intensity limit exceeded
111    pub(crate) fn record_restart(&mut self) -> bool {
112        let now = Instant::now();
113        let cutoff = now
114            .checked_sub(Duration::from_secs(self.intensity.within_seconds))
115            .unwrap_or(now);
116
117        // Remove old restarts outside the time window
118        while let Some(&time) = self.restart_times.front() {
119            if time < cutoff {
120                self.restart_times.pop_front();
121            } else {
122                break;
123            }
124        }
125
126        self.restart_times.push_back(now);
127
128        // Check if we've exceeded the limit
129        self.restart_times.len() > self.intensity.max_restarts
130    }
131
132    #[allow(dead_code)]
133    pub(crate) fn reset(&mut self) {
134        self.restart_times.clear();
135    }
136}