//! Task state machine. use std::fmt; /// Task execution state. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum TaskState { /// Task received, preparing overlay. Initializing, /// Overlay ready, starting container. Starting, /// Container running. Running, /// Container paused. Paused, /// Waiting for sibling or resource. Blocked, /// Task completed successfully. Completed, /// Task failed with error. Failed, /// Task interrupted by user. Interrupted, } impl TaskState { /// Check if a state transition is valid. pub fn can_transition_to(&self, target: TaskState) -> bool { use TaskState::*; matches!( (self, target), // From Initializing (Initializing, Starting) | (Initializing, Failed) | (Initializing, Interrupted) // From Starting | (Starting, Running) | (Starting, Failed) | (Starting, Interrupted) // From Running | (Running, Paused) | (Running, Blocked) | (Running, Completed) | (Running, Failed) | (Running, Interrupted) // From Paused | (Paused, Running) | (Paused, Interrupted) | (Paused, Failed) // From Blocked | (Blocked, Running) | (Blocked, Failed) | (Blocked, Interrupted) ) } /// Check if this state is terminal (no more transitions possible). pub fn is_terminal(&self) -> bool { matches!( self, TaskState::Completed | TaskState::Failed | TaskState::Interrupted ) } /// Check if the task is currently active (running or paused). pub fn is_active(&self) -> bool { matches!( self, TaskState::Initializing | TaskState::Starting | TaskState::Running | TaskState::Paused | TaskState::Blocked ) } /// Check if the task is running. pub fn is_running(&self) -> bool { matches!(self, TaskState::Running) } /// Convert to string for protocol messages. pub fn as_str(&self) -> &'static str { match self { TaskState::Initializing => "initializing", TaskState::Starting => "starting", TaskState::Running => "running", TaskState::Paused => "paused", TaskState::Blocked => "blocked", TaskState::Completed => "done", TaskState::Failed => "failed", TaskState::Interrupted => "interrupted", } } /// Parse from string. pub fn from_str(s: &str) -> Option { match s.to_lowercase().as_str() { "initializing" => Some(TaskState::Initializing), "starting" => Some(TaskState::Starting), "running" => Some(TaskState::Running), "paused" => Some(TaskState::Paused), "blocked" => Some(TaskState::Blocked), "done" | "completed" => Some(TaskState::Completed), "failed" => Some(TaskState::Failed), "interrupted" => Some(TaskState::Interrupted), _ => None, } } } impl fmt::Display for TaskState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl Default for TaskState { fn default() -> Self { TaskState::Initializing } } #[cfg(test)] mod tests { #[allow(unused_imports)] use crate::daemon::*; use super::TaskState; #[test] fn test_valid_transitions() { use TaskState::*; // Valid transitions assert!(Initializing.can_transition_to(Starting)); assert!(Starting.can_transition_to(Running)); assert!(Running.can_transition_to(Completed)); assert!(Running.can_transition_to(Paused)); assert!(Paused.can_transition_to(Running)); // Invalid transitions assert!(!Completed.can_transition_to(Running)); assert!(!Failed.can_transition_to(Running)); assert!(!Running.can_transition_to(Initializing)); } #[test] fn test_terminal_states() { assert!(TaskState::Completed.is_terminal()); assert!(TaskState::Failed.is_terminal()); assert!(TaskState::Interrupted.is_terminal()); assert!(!TaskState::Running.is_terminal()); assert!(!TaskState::Paused.is_terminal()); } #[test] fn test_parse() { assert_eq!(TaskState::from_str("running"), Some(TaskState::Running)); assert_eq!(TaskState::from_str("done"), Some(TaskState::Completed)); assert_eq!(TaskState::from_str("invalid"), None); } }