Skip to main content

rust_supervisor/runtime/
supervisor.rs

1//! Runtime supervisor entry point.
2//!
3//! This module validates supervisor declarations, derives runtime options, and
4//! returns a [`crate::control::handle::SupervisorHandle`].
5
6use crate::config::state::ConfigState;
7use crate::control::handle::SupervisorHandle;
8use crate::dashboard::config::validate_dashboard_ipc_config;
9use crate::dashboard::error::DashboardError;
10use crate::dashboard::runtime::start_dashboard_ipc_runtime;
11use crate::error::types::SupervisorError;
12use crate::runtime::control_loop::{RuntimeControlState, run_control_loop};
13use crate::shutdown::stage::ShutdownPolicy;
14use crate::spec::supervisor::SupervisorSpec;
15use std::path::Path;
16use tokio::sync::{broadcast, mpsc};
17
18/// Supervisor runtime entry point.
19#[derive(Debug, Clone, Copy, Default)]
20pub struct Supervisor;
21
22impl Supervisor {
23    /// Starts a supervisor runtime from an owned specification value.
24    ///
25    /// # Arguments
26    ///
27    /// - `spec`: Supervisor specification owned by the caller.
28    ///
29    /// # Returns
30    ///
31    /// Returns a [`SupervisorHandle`] connected to the runtime control loop.
32    pub async fn start(spec: SupervisorSpec) -> Result<SupervisorHandle, SupervisorError> {
33        let shutdown_policy = shutdown_policy_from_spec(&spec);
34        Self::start_with_policy(spec, shutdown_policy).await
35    }
36
37    /// Starts a supervisor runtime from validated configuration state.
38    ///
39    /// # Arguments
40    ///
41    /// - `state`: Validated configuration state owned by the caller.
42    ///
43    /// # Returns
44    ///
45    /// Returns a [`SupervisorHandle`] only after configuration has produced a
46    /// valid supervisor specification.
47    pub async fn start_from_config_state(
48        state: ConfigState,
49    ) -> Result<SupervisorHandle, SupervisorError> {
50        let ipc_config = state.ipc.clone();
51        let spec = state.to_supervisor_spec()?;
52        let mut handle = Self::start(spec.clone()).await?;
53        let dashboard_config =
54            validate_dashboard_ipc_config(ipc_config.as_ref()).map_err(dashboard_startup_error)?;
55        if let Some(dashboard_config) = dashboard_config {
56            let dashboard_runtime =
57                start_dashboard_ipc_runtime(dashboard_config, spec, handle.clone())
58                    .map_err(dashboard_startup_error)?;
59            handle = handle.with_dashboard_runtime(dashboard_runtime);
60        }
61        Ok(handle)
62    }
63
64    /// Starts a supervisor runtime from a YAML configuration file.
65    ///
66    /// # Arguments
67    ///
68    /// - `path`: Path to the YAML configuration file.
69    ///
70    /// # Returns
71    ///
72    /// Returns a [`SupervisorHandle`] only after the configuration file has
73    /// loaded and validated successfully.
74    pub async fn start_from_config_file(
75        path: impl AsRef<Path>,
76    ) -> Result<SupervisorHandle, SupervisorError> {
77        let state = crate::config::loader::load_config_state(path)?;
78        Self::start_from_config_state(state).await
79    }
80
81    /// Starts a supervisor runtime with an explicit shutdown policy.
82    ///
83    /// # Arguments
84    ///
85    /// - `spec`: Supervisor specification owned by the caller.
86    /// - `shutdown_policy`: Policy used by the control loop.
87    ///
88    /// # Returns
89    ///
90    /// Returns a [`SupervisorHandle`] connected to the runtime control loop.
91    pub async fn start_with_policy(
92        spec: SupervisorSpec,
93        shutdown_policy: ShutdownPolicy,
94    ) -> Result<SupervisorHandle, SupervisorError> {
95        spec.validate()?;
96        let (command_sender, command_receiver) = mpsc::channel(spec.control_channel_capacity);
97        let (event_sender, _) = broadcast::channel(spec.event_channel_capacity);
98        let state = RuntimeControlState::new(spec, shutdown_policy, command_sender.clone())?;
99        tokio::spawn(run_control_loop(
100            state,
101            command_receiver,
102            event_sender.clone(),
103        ));
104        Ok(SupervisorHandle::new(command_sender, event_sender))
105    }
106}
107
108/// Builds the shutdown policy from supervisor defaults.
109///
110/// # Arguments
111///
112/// - `spec`: Supervisor declaration that owns default shutdown values.
113///
114/// # Returns
115///
116/// Returns a [`ShutdownPolicy`] for runtime shutdown coordination.
117fn shutdown_policy_from_spec(spec: &SupervisorSpec) -> ShutdownPolicy {
118    ShutdownPolicy::new(
119        spec.default_shutdown_policy.graceful_timeout,
120        spec.default_shutdown_policy.abort_wait,
121        true,
122    )
123}
124
125/// Converts dashboard startup failures into supervisor startup errors.
126fn dashboard_startup_error(error: DashboardError) -> SupervisorError {
127    SupervisorError::fatal_config(format!("dashboard IPC startup failed: {error}"))
128}