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}