Skip to main content

shep_core/
status.rs

1//! Process lifecycle status
2
3use core::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// Lifecycle state of a sheep (one managed process)
8///
9/// The serialized strings are the wire contract; `waiting-restart` means a
10/// backoff or restart delay is pending.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "kebab-case")]
13pub enum ProcStatus {
14    /// Spawned, not yet ready
15    Starting,
16    /// Running and (if configured) ready
17    Online,
18    /// This instance is going away and is not a restart target
19    ///
20    /// Reachable from exactly one path: a reload's `SpawnNew` step, which
21    /// marks the instance being replaced before its replacement is spawned,
22    /// so the two never both count as running. Nothing else sets it — an
23    /// operator's `stop` leaves a sheep `Online` for its whole kill ladder
24    /// instead, so this status names reload's transient specifically, never
25    /// "any kill ladder in progress". A scheduled restart or an out-of-band
26    /// liveness/memory-limit restart must both reject a sheep in this status
27    /// rather than race the fresh replacement coming to take over its slot.
28    Stopping,
29    /// Cleanly stopped; not scheduled to run
30    Stopped,
31    /// Restart budget exhausted or spawn failed
32    Errored,
33    /// Restart pending after a backoff or configured delay
34    WaitingRestart,
35}
36
37impl fmt::Display for ProcStatus {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.write_str(match self {
40            Self::Starting => "starting",
41            Self::Online => "online",
42            Self::Stopping => "stopping",
43            Self::Stopped => "stopped",
44            Self::Errored => "errored",
45            Self::WaitingRestart => "waiting-restart",
46        })
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn wire_strings_are_stable() {
56        let cases = [
57            (ProcStatus::Starting, "\"starting\""),
58            (ProcStatus::Online, "\"online\""),
59            (ProcStatus::Stopping, "\"stopping\""),
60            (ProcStatus::Stopped, "\"stopped\""),
61            (ProcStatus::Errored, "\"errored\""),
62            (ProcStatus::WaitingRestart, "\"waiting-restart\""),
63        ];
64        for (status, json) in cases {
65            assert_eq!(serde_json::to_string(&status).unwrap(), json);
66            assert_eq!(serde_json::from_str::<ProcStatus>(json).unwrap(), status);
67            assert_eq!(format!("\"{status}\""), json);
68        }
69    }
70}