zinit 0.3.7

Process supervisor with dependency management
Documentation
//! Supervisor event types and related enums.

use crate::sdk::{PrepareRestartResult, ReloadResult, ServiceConfig};
use tokio::sync::oneshot;

use crate::server::graph::ServiceId;

/// Events that can occur in the supervisor.
#[allow(clippy::large_enum_variant)]
pub enum SupervisorEvent {
    /// A process exited.
    ProcessExited {
        service_id: ServiceId,
        exit_code: Option<i32>,
        signal: Option<i32>,
    },
    /// Health check completed.
    HealthCheckResult {
        service_id: ServiceId,
        passed: bool,
        error: Option<String>,
    },
    /// A timeout fired.
    Timeout {
        service_id: ServiceId,
        kind: TimeoutKind,
    },
    /// Re-evaluate a service for possible start.
    Reevaluate { service_id: ServiceId },
    /// Start a service by name.
    StartService { name: String },
    /// Stop a service by name.
    StopService { name: String },
    /// Restart a service by name (stop, then start when exited).
    RestartService { name: String },
    /// Send a signal to a service.
    KillService {
        name: String,
        signal: Option<String>,
    },
    /// Add a service (validation already done by IPC).
    AddService { config: ServiceConfig },
    /// Remove a service by name.
    RemoveService { name: String },
    /// Reload configuration from disk.
    Reload {
        response_tx: oneshot::Sender<Result<ReloadResult, String>>,
    },
    /// Prepare for restart - save state and respond.
    PrepareRestart {
        response_tx: oneshot::Sender<Result<PrepareRestartResult, String>>,
    },
    /// A builtin service completed.
    BuiltinCompleted {
        service_id: ServiceId,
        success: bool,
        error: Option<String>,
    },
}

impl std::fmt::Debug for SupervisorEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ProcessExited {
                service_id,
                exit_code,
                signal,
            } => f
                .debug_struct("ProcessExited")
                .field("service_id", service_id)
                .field("exit_code", exit_code)
                .field("signal", signal)
                .finish(),
            Self::HealthCheckResult {
                service_id,
                passed,
                error,
            } => f
                .debug_struct("HealthCheckResult")
                .field("service_id", service_id)
                .field("passed", passed)
                .field("error", error)
                .finish(),
            Self::Timeout { service_id, kind } => f
                .debug_struct("Timeout")
                .field("service_id", service_id)
                .field("kind", kind)
                .finish(),
            Self::Reevaluate { service_id } => f
                .debug_struct("Reevaluate")
                .field("service_id", service_id)
                .finish(),
            Self::StartService { name } => {
                f.debug_struct("StartService").field("name", name).finish()
            }
            Self::StopService { name } => {
                f.debug_struct("StopService").field("name", name).finish()
            }
            Self::RestartService { name } => f
                .debug_struct("RestartService")
                .field("name", name)
                .finish(),
            Self::KillService { name, signal } => f
                .debug_struct("KillService")
                .field("name", name)
                .field("signal", signal)
                .finish(),
            Self::AddService { config } => f
                .debug_struct("AddService")
                .field("name", &config.service.name)
                .finish(),
            Self::RemoveService { name } => {
                f.debug_struct("RemoveService").field("name", name).finish()
            }
            Self::Reload { .. } => write!(f, "Reload"),
            Self::PrepareRestart { .. } => write!(f, "PrepareRestart"),
            Self::BuiltinCompleted {
                service_id,
                success,
                error,
            } => f
                .debug_struct("BuiltinCompleted")
                .field("service_id", service_id)
                .field("success", success)
                .field("error", error)
                .finish(),
        }
    }
}

impl std::fmt::Display for SupervisorEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ProcessExited {
                exit_code, signal, ..
            } => {
                write!(
                    f,
                    "ProcessExited(code={:?}, signal={:?})",
                    exit_code, signal
                )
            }
            Self::HealthCheckResult { passed, .. } => {
                write!(f, "HealthCheckResult(passed={})", passed)
            }
            Self::Timeout { kind, .. } => write!(f, "Timeout({:?})", kind),
            Self::Reevaluate { .. } => write!(f, "Reevaluate"),
            Self::StartService { name } => write!(f, "StartService({})", name),
            Self::StopService { name } => write!(f, "StopService({})", name),
            Self::RestartService { name } => write!(f, "RestartService({})", name),
            Self::KillService { name, signal } => {
                write!(f, "KillService({}, {:?})", name, signal)
            }
            Self::AddService { config } => write!(f, "AddService({})", config.service.name),
            Self::RemoveService { name } => write!(f, "RemoveService({})", name),
            Self::Reload { .. } => write!(f, "Reload"),
            Self::PrepareRestart { .. } => write!(f, "PrepareRestart"),
            Self::BuiltinCompleted { success, .. } => {
                write!(f, "BuiltinCompleted(success={})", success)
            }
        }
    }
}

/// Types of timeouts.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimeoutKind {
    Start,
    Stop,
    HealthCheck,
    RestartDelay,
}

/// Action to take after re-evaluating a service.
pub(crate) enum ReevaluateAction {
    /// No action needed.
    None,
    /// Try to start the service.
    TryStart,
    /// Downgrade a target that lost its dependencies.
    DowngradeTarget {
        name: String,
        waiting_on: Vec<String>,
    },
}

/// Internal action to take after evaluating a service for start.
#[allow(clippy::large_enum_variant)]
pub(crate) enum StartAction {
    TargetReady {
        name: String,
        dependents: Vec<ServiceId>,
    },
    SpawnProcess {
        config: Option<ServiceConfig>,
    },
    Blocked {
        name: String,
        reason: String,
    },
}