545dd59c72
Nuevo crate runtime/sandokan-lifecycle: lógica pura reutilizable por cualquier supervisor de procesos (shuma, matilda Ghost, charka-shadow, mirada). Sin syscalls, sin proceso, sin UI. Módulos: - backoff — Backoff exponencial con tope - ttl — Ttl anclado a Instant - quota — ResourceQuota + check_quota + Breach + QuotaAction - restart — RestartPolicy + RestartTracker (conteo + backoff) - state — LifecycleState (Pending/Running/Exited/Failed/Killed) 15 tests verdes. cargo check --workspace verde. Variante segura de A4: se crea la library limpia sin tocar shuma-core (módulo maduro). La migración de WorkspaceManager a consumir estas primitivas queda registrada como A4.2 (refactor diferido, no urgente). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
66 lines
2.1 KiB
Rust
66 lines
2.1 KiB
Rust
//! Máquina de estados del ciclo de vida de una entidad supervisada.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Estado de una entidad supervisada (proceso, workspace, sandbox, ...).
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum LifecycleState {
|
|
/// Creada, aún no arrancó.
|
|
Pending,
|
|
/// En ejecución.
|
|
Running,
|
|
/// Salió por sí misma con un código de salida.
|
|
Exited { code: i32 },
|
|
/// Falló (no llegó a correr, o crasheó de forma no capturable).
|
|
Failed { reason: String },
|
|
/// Terminada por el supervisor (SIGKILL / quota / drain).
|
|
Killed,
|
|
}
|
|
|
|
impl LifecycleState {
|
|
/// `true` si el estado es terminal (no habrá más transiciones sin
|
|
/// un restart explícito).
|
|
pub fn is_terminal(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
LifecycleState::Exited { .. }
|
|
| LifecycleState::Failed { .. }
|
|
| LifecycleState::Killed
|
|
)
|
|
}
|
|
|
|
/// `true` si el estado terminal cuenta como fallo (dispara restart
|
|
/// si la política lo permite). `Exited { code: 0 }` NO es fallo.
|
|
pub fn is_failure(&self) -> bool {
|
|
match self {
|
|
LifecycleState::Exited { code } => *code != 0,
|
|
LifecycleState::Failed { .. } => true,
|
|
LifecycleState::Killed => false, // kill deliberado, no fallo
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn terminal_detection() {
|
|
assert!(!LifecycleState::Pending.is_terminal());
|
|
assert!(!LifecycleState::Running.is_terminal());
|
|
assert!(LifecycleState::Exited { code: 0 }.is_terminal());
|
|
assert!(LifecycleState::Killed.is_terminal());
|
|
assert!(LifecycleState::Failed { reason: "x".into() }.is_terminal());
|
|
}
|
|
|
|
#[test]
|
|
fn failure_semantics() {
|
|
assert!(!LifecycleState::Exited { code: 0 }.is_failure());
|
|
assert!(LifecycleState::Exited { code: 1 }.is_failure());
|
|
assert!(LifecycleState::Failed { reason: "x".into() }.is_failure());
|
|
assert!(!LifecycleState::Killed.is_failure());
|
|
assert!(!LifecycleState::Running.is_failure());
|
|
}
|
|
}
|