summaryrefslogblamecommitdiff
path: root/makima/src/daemon/task/state.rs
blob: fe73de1ba81fa21c8f5ba43971b890502266af18 (plain) (tree)





























































































































                                                                             
                 

































                                                                             
//! 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<Self> {
        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 {
    use super::*;

    #[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);
    }
}