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}