Skip to main content

appcore_supervisor/
policy.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: policy.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/07/24 11:51:10 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/24 11:51:10 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Restart and shutdown policy.
12
13use crate::{SupervisorError, SupervisorResult};
14use std::time::Duration;
15
16/// Conditions under which a managed service may restart.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum RestartMode {
19    /// Never restart automatically.
20    Never,
21    /// Restart only after a failed health signal or unexpected exit.
22    OnFailure,
23    /// Restart after any unexpected service exit.
24    Always,
25}
26
27/// Bounded restart and shutdown behavior for one service.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct RestartPolicy {
30    /// Automatic restart condition.
31    pub mode: RestartMode,
32    /// Maximum restart attempts inside `restart_window`.
33    pub restart_budget: u32,
34    /// Sliding time window applied to the restart budget.
35    pub restart_window: Duration,
36    /// Base delay before a restart.
37    pub backoff: Duration,
38    /// Maximum random delay added to the base backoff.
39    pub jitter: Duration,
40    /// Cooperative shutdown deadline.
41    pub shutdown_timeout: Duration,
42}
43
44impl RestartPolicy {
45    /// Creates a policy that never restarts automatically.
46    pub fn never() -> Self {
47        Self {
48            mode: RestartMode::Never,
49            restart_budget: 0,
50            restart_window: Duration::from_secs(600),
51            backoff: Duration::ZERO,
52            jitter: Duration::ZERO,
53            shutdown_timeout: Duration::from_secs(10),
54        }
55    }
56
57    /// Creates the default bounded on-failure policy.
58    pub fn bounded(restart_budget: u32, restart_window: Duration) -> SupervisorResult<Self> {
59        let policy = Self {
60            mode: RestartMode::OnFailure,
61            restart_budget,
62            restart_window,
63            backoff: Duration::from_millis(100),
64            jitter: Duration::from_millis(50),
65            shutdown_timeout: Duration::from_secs(10),
66        };
67        policy.validate()?;
68        Ok(policy)
69    }
70
71    /// Replaces backoff and jitter bounds.
72    pub fn with_backoff(mut self, backoff: Duration, jitter: Duration) -> Self {
73        self.backoff = backoff;
74        self.jitter = jitter;
75        self
76    }
77
78    /// Replaces the cooperative shutdown deadline.
79    pub fn with_shutdown_timeout(mut self, shutdown_timeout: Duration) -> Self {
80        self.shutdown_timeout = shutdown_timeout;
81        self
82    }
83
84    /// Validates policy bounds.
85    pub fn validate(&self) -> SupervisorResult<()> {
86        if self.mode != RestartMode::Never
87            && (self.restart_budget == 0 || self.restart_window.is_zero())
88        {
89            return Err(SupervisorError::InvalidConfiguration(
90                "automatic restart requires a non-zero budget and window".to_string(),
91            ));
92        }
93        if self.shutdown_timeout.is_zero() {
94            return Err(SupervisorError::InvalidConfiguration(
95                "shutdown_timeout must be greater than zero".to_string(),
96            ));
97        }
98        Ok(())
99    }
100}
101
102impl Default for RestartPolicy {
103    fn default() -> Self {
104        Self {
105            mode: RestartMode::OnFailure,
106            restart_budget: 5,
107            restart_window: Duration::from_secs(600),
108            backoff: Duration::from_millis(100),
109            jitter: Duration::from_millis(50),
110            shutdown_timeout: Duration::from_secs(10),
111        }
112    }
113}