Skip to main content

elfo_core/restarting/
restart_policy.rs

1use std::{num::NonZeroU64, time::Duration};
2
3use tracing::warn;
4
5use crate::ActorStatus;
6
7/// The behaviour on actor termination.
8#[derive(Debug, Clone, PartialEq)]
9pub struct RestartPolicy {
10    pub(crate) mode: RestartMode,
11}
12
13impl Default for RestartPolicy {
14    fn default() -> Self {
15        Self::never()
16    }
17}
18
19#[derive(Debug, Clone, PartialEq)]
20pub(crate) enum RestartMode {
21    Always(RestartParams),
22    OnFailure(RestartParams),
23    Never,
24}
25
26impl RestartPolicy {
27    pub fn always(restart_params: RestartParams) -> Self {
28        Self {
29            mode: RestartMode::Always(restart_params),
30        }
31    }
32
33    pub fn on_failure(restart_params: RestartParams) -> Self {
34        Self {
35            mode: RestartMode::OnFailure(restart_params),
36        }
37    }
38
39    pub fn never() -> Self {
40        Self {
41            mode: RestartMode::Never,
42        }
43    }
44
45    pub(crate) fn restarting_allowed(&self, status: &ActorStatus) -> bool {
46        match &self.mode {
47            RestartMode::Always(_) => true,
48            RestartMode::OnFailure(_) => status.kind().is_failed(),
49            _ => false,
50        }
51    }
52
53    pub(crate) fn restart_params(&self) -> Option<RestartParams> {
54        match &self.mode {
55            RestartMode::Always(params) | RestartMode::OnFailure(params) => Some(*params),
56            _ => None,
57        }
58    }
59}
60
61/// Restart parameters for the backoff strategy when an actor restarts based on
62/// the [RestartPolicy].
63#[derive(Debug, Copy, Clone, PartialEq)]
64pub struct RestartParams {
65    pub(crate) min_backoff: Duration,
66    pub(crate) max_backoff: Duration,
67    pub(crate) auto_reset: Duration,
68    pub(crate) max_retries: NonZeroU64,
69    pub(crate) factor: f64,
70}
71
72impl RestartParams {
73    /// Creates a new instance with the specified minimum and maximum backoff
74    /// durations. The default values for `auto_reset`, `max_retries`, and
75    /// `factor` are set as follows:
76    /// - `auto_reset = min_backoff`
77    /// - `max_retries = NonZeroU64::MAX`
78    /// - `factor = 2.0`
79    pub fn new(min_backoff: Duration, max_backoff: Duration) -> Self {
80        RestartParams {
81            min_backoff,
82            max_backoff: min_backoff.max(max_backoff),
83            auto_reset: min_backoff,
84            max_retries: NonZeroU64::MAX,
85            factor: 2.0,
86        }
87    }
88
89    /// Sets the duration deemed sufficient to consider an actor healthy. Once
90    /// this duration elapses, the backoff strategy automatically resets,
91    /// including retry counting, effectively treating the next attempt as the
92    /// first retry. Therefore, setting the `auto_reset` to small values,
93    /// such as [Duration::ZERO], can result in the absence of a limit on
94    /// the maximum number of retries. After the backoff strategy resets, the
95    /// actor will restart immediately.
96    ///
97    /// `None` does not change the `auto_reset` setting.
98    ///
99    /// If the function isn't used, `auto_reset = min_backoff` is used by
100    /// default.
101    pub fn auto_reset(self, auto_reset: impl Into<Option<Duration>>) -> Self {
102        Self {
103            auto_reset: auto_reset.into().unwrap_or(self.auto_reset),
104            ..self
105        }
106    }
107
108    /// Sets the factor used to calculate the next backoff duration.
109    /// The factor should be a finite value and should not be negative;
110    /// otherwise, a warning will be emitted.
111    ///
112    /// `None` value does not change the `factor` setting.
113    ///
114    /// If the function isn't used, `factor = 2.0` is used by default.
115    pub fn factor(self, factor: impl Into<Option<f64>>) -> Self {
116        let factor = factor.into().unwrap_or(self.factor);
117        let factor = if !factor.is_finite() || factor.is_sign_negative() {
118            warn!("factor should be a finite value and should not be negative");
119            0.0
120        } else {
121            factor
122        };
123        Self { factor, ..self }
124    }
125
126    /// Sets the maximum number of allowed retries. Each time the actor
127    /// restarts, it counts as a retry. If the retries reach the specified
128    /// max_retries, the actor stops restarting. If the actor lives long
129    /// enough to be considered healthy (see [RestartParams::auto_reset]), the
130    /// restart count goes back to zero, and the next restart is considered
131    /// the first retry again.
132    ///
133    /// `None` does not change the `max_retries` setting.
134    ///
135    /// If the function isn't used, `max_retries = NonZeroU64::MAX` is used by
136    /// default.
137    pub fn max_retries(self, max_retries: impl Into<Option<NonZeroU64>>) -> Self {
138        Self {
139            max_retries: max_retries.into().unwrap_or(self.max_retries),
140            ..self
141        }
142    }
143}