Skip to main content

rust_supervisor/state/
child.rs

1//! Child current-state model.
2//!
3//! The module owns the state visible through current-state queries. It keeps
4//! lifecycle history out of state and stores only the latest operational facts.
5
6use crate::error::types::TaskFailure;
7use crate::event::payload::PolicyDecision;
8use crate::event::time::EventSequence;
9use crate::id::types::{Attempt, ChildId, Generation, SupervisorPath};
10use serde::{Deserialize, Serialize};
11
12/// Lifecycle phase for a child.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum ChildLifecycleState {
15    /// Child was declared but not started.
16    Declared,
17    /// Child is starting.
18    Starting,
19    /// Child is running.
20    Running,
21    /// Child reported readiness.
22    Ready,
23    /// Child is restarting after a policy decision.
24    Restarting,
25    /// Child is paused by control command.
26    Paused,
27    /// Child is isolated from automatic restart.
28    Quarantined,
29    /// Child is shutting down.
30    ShuttingDown,
31    /// Child stopped without an active failure.
32    Stopped,
33    /// Child failed and is terminal for automatic restart.
34    Failed,
35}
36
37impl ChildLifecycleState {
38    /// Returns a low-cardinality state label.
39    ///
40    /// # Arguments
41    ///
42    /// This function has no arguments.
43    ///
44    /// # Returns
45    ///
46    /// Returns a stable state label.
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// let state = rust_supervisor::state::child::ChildLifecycleState::Ready;
52    /// assert_eq!(state.as_label(), "ready");
53    /// ```
54    pub fn as_label(&self) -> &'static str {
55        match self {
56            Self::Declared => "declared",
57            Self::Starting => "starting",
58            Self::Running => "running",
59            Self::Ready => "ready",
60            Self::Restarting => "restarting",
61            Self::Paused => "paused",
62            Self::Quarantined => "quarantined",
63            Self::ShuttingDown => "shutting_down",
64            Self::Stopped => "stopped",
65            Self::Failed => "failed",
66        }
67    }
68
69    /// Reports whether automatic restart treats the state as terminal.
70    ///
71    /// # Arguments
72    ///
73    /// This function has no arguments.
74    ///
75    /// # Returns
76    ///
77    /// Returns `true` for terminal states.
78    pub fn is_terminal(&self) -> bool {
79        matches!(self, Self::Quarantined | Self::Stopped | Self::Failed)
80    }
81}
82
83/// Health status visible in current state.
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub enum ChildHealth {
86    /// No health signal has been reported.
87    Unknown,
88    /// Latest health signal is healthy.
89    Healthy,
90    /// Latest health signal is stale.
91    Stale,
92    /// Latest health signal is unhealthy.
93    Unhealthy,
94}
95
96/// Readiness status visible in current state.
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub enum ChildReadiness {
99    /// Readiness is not configured or has not been requested.
100    NotRequired,
101    /// Explicit readiness is still pending.
102    Pending,
103    /// Child is ready.
104    Ready,
105}
106
107/// Current state for one child.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109pub struct ChildState {
110    /// Stable tree path for the child.
111    pub path: SupervisorPath,
112    /// Stable child identifier.
113    pub id: ChildId,
114    /// Human-readable child name.
115    pub name: String,
116    /// Current lifecycle state.
117    pub state: ChildLifecycleState,
118    /// Current health status.
119    pub health: ChildHealth,
120    /// Current generation.
121    pub generation: Generation,
122    /// Current attempt.
123    pub attempt: Attempt,
124    /// Restart count inside the active restart window.
125    pub restart_count: u64,
126    /// Last typed task failure.
127    pub last_failure: Option<TaskFailure>,
128    /// Last event sequence that changed this state.
129    pub last_event_sequence: Option<EventSequence>,
130    /// Last policy decision produced for this child.
131    pub last_policy_decision: Option<PolicyDecision>,
132    /// Current readiness status.
133    pub readiness: ChildReadiness,
134}
135
136impl ChildState {
137    /// Creates a declared child state.
138    ///
139    /// # Arguments
140    ///
141    /// - `path`: Stable child path in the supervisor tree.
142    /// - `id`: Stable child identifier.
143    /// - `name`: Human-readable child name.
144    ///
145    /// # Returns
146    ///
147    /// Returns a [`ChildState`] in the declared phase.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// let state = rust_supervisor::state::child::ChildState::declared(
153    ///     rust_supervisor::id::types::SupervisorPath::root().join("worker"),
154    ///     rust_supervisor::id::types::ChildId::new("worker"),
155    ///     "Worker",
156    /// );
157    /// assert_eq!(state.state.as_label(), "declared");
158    /// ```
159    pub fn declared(path: SupervisorPath, id: ChildId, name: impl Into<String>) -> Self {
160        Self {
161            path,
162            id,
163            name: name.into(),
164            state: ChildLifecycleState::Declared,
165            health: ChildHealth::Unknown,
166            generation: Generation::initial(),
167            attempt: Attempt::first(),
168            restart_count: 0,
169            last_failure: None,
170            last_event_sequence: None,
171            last_policy_decision: None,
172            readiness: ChildReadiness::Pending,
173        }
174    }
175
176    /// Returns a state with a new lifecycle phase and event sequence.
177    ///
178    /// # Arguments
179    ///
180    /// - `state`: New lifecycle state.
181    /// - `sequence`: Event sequence that caused the change.
182    ///
183    /// # Returns
184    ///
185    /// Returns an updated [`ChildState`].
186    pub fn with_lifecycle_state(
187        mut self,
188        state: ChildLifecycleState,
189        sequence: EventSequence,
190    ) -> Self {
191        self.state = state;
192        self.last_event_sequence = Some(sequence);
193        self
194    }
195
196    /// Marks the child as ready.
197    ///
198    /// # Arguments
199    ///
200    /// - `sequence`: Event sequence that reported readiness.
201    ///
202    /// # Returns
203    ///
204    /// Returns an updated [`ChildState`].
205    pub fn mark_ready(mut self, sequence: EventSequence) -> Self {
206        self.state = ChildLifecycleState::Ready;
207        self.readiness = ChildReadiness::Ready;
208        self.last_event_sequence = Some(sequence);
209        self
210    }
211
212    /// Records a typed failure.
213    ///
214    /// # Arguments
215    ///
216    /// - `failure`: Failure reported by the task.
217    /// - `sequence`: Event sequence that reported the failure.
218    ///
219    /// # Returns
220    ///
221    /// Returns an updated [`ChildState`].
222    pub fn record_failure(mut self, failure: TaskFailure, sequence: EventSequence) -> Self {
223        self.state = ChildLifecycleState::Failed;
224        self.health = ChildHealth::Unhealthy;
225        self.last_failure = Some(failure);
226        self.last_event_sequence = Some(sequence);
227        self
228    }
229
230    /// Records a policy decision and restart count.
231    ///
232    /// # Arguments
233    ///
234    /// - `decision`: Policy decision attached to the child.
235    /// - `restart_count`: Restart count after the decision.
236    ///
237    /// # Returns
238    ///
239    /// Returns an updated [`ChildState`].
240    pub fn with_policy_decision(mut self, decision: PolicyDecision, restart_count: u64) -> Self {
241        self.last_policy_decision = Some(decision);
242        self.restart_count = restart_count;
243        self
244    }
245}