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