rust_supervisor/runtime/
supervisor.rs1use crate::config::state::ConfigState;
7use crate::control::handle::SupervisorHandle;
8#[cfg(unix)]
9use crate::dashboard::{
10 config::validate_dashboard_ipc_config, error::DashboardError,
11 runtime::start_dashboard_ipc_runtime,
12};
13use crate::error::types::SupervisorError;
14use crate::observe::pipeline::ObservabilityPipeline;
15use crate::runtime::control_loop::{RuntimeControlState, run_control_loop};
16use crate::runtime::lifecycle::RuntimeControlPlane;
17use crate::runtime::watchdog::RuntimeWatchdog;
18use crate::spec::supervisor::SupervisorSpec;
19use crate::task::factory_registry::TaskFactoryRegistry;
20use std::path::Path;
21use std::sync::{Arc, Mutex};
22use tokio::sync::{broadcast, mpsc};
23
24#[derive(Debug, Clone, Copy, Default)]
26pub struct Supervisor;
27
28impl Supervisor {
29 pub async fn start(spec: SupervisorSpec) -> Result<SupervisorHandle, SupervisorError> {
39 Self::start_with_factory_registry(spec, TaskFactoryRegistry::new()).await
40 }
41
42 pub async fn start_from_config_state(
53 state: ConfigState,
54 ) -> Result<SupervisorHandle, SupervisorError> {
55 #[cfg(unix)]
56 let audit_config = state.audit.clone();
57 #[cfg(unix)]
58 let dashboard_config = state.dashboard.clone();
59 let spec = state.to_supervisor_spec()?;
60 #[cfg(unix)]
61 let handle = Self::start(spec.clone()).await?;
62 #[cfg(not(unix))]
63 let handle = Self::start(spec).await?;
64 #[cfg(unix)]
65 let handle = if let Some(dashboard_config) =
66 validate_dashboard_ipc_config(dashboard_config.as_ref())
67 .map_err(dashboard_startup_error)?
68 {
69 let dashboard_runtime =
70 start_dashboard_ipc_runtime(dashboard_config, audit_config, spec, handle.clone())
71 .map_err(dashboard_startup_error)?;
72 handle.with_dashboard_runtime(dashboard_runtime)
73 } else {
74 handle
75 };
76 Ok(handle)
77 }
78
79 pub async fn start_from_config_state_with_factories(
91 state: ConfigState,
92 task_factory_registry: TaskFactoryRegistry,
93 ) -> Result<SupervisorHandle, SupervisorError> {
94 #[cfg(unix)]
95 let audit_config = state.audit.clone();
96 #[cfg(unix)]
97 let dashboard_config = state.dashboard.clone();
98 let spec = state.to_supervisor_spec_with_factories(&task_factory_registry)?;
99 #[cfg(unix)]
100 let handle =
101 Self::start_with_factory_registry(spec.clone(), task_factory_registry.clone()).await?;
102 #[cfg(not(unix))]
103 let handle = Self::start_with_factory_registry(spec, task_factory_registry).await?;
104 #[cfg(unix)]
105 let handle = if let Some(dashboard_config) =
106 validate_dashboard_ipc_config(dashboard_config.as_ref())
107 .map_err(dashboard_startup_error)?
108 {
109 let dashboard_runtime =
110 start_dashboard_ipc_runtime(dashboard_config, audit_config, spec, handle.clone())
111 .map_err(dashboard_startup_error)?;
112 handle.with_dashboard_runtime(dashboard_runtime)
113 } else {
114 handle
115 };
116 Ok(handle)
117 }
118
119 pub async fn start_from_config_file(
130 path: impl AsRef<Path>,
131 ) -> Result<SupervisorHandle, SupervisorError> {
132 let state = crate::config::loader::load_config_from_yaml_file(path)?;
133 Self::start_from_config_state(state).await
134 }
135
136 pub async fn start_from_config_file_with_factories(
147 path: impl AsRef<Path>,
148 task_factory_registry: TaskFactoryRegistry,
149 ) -> Result<SupervisorHandle, SupervisorError> {
150 let state = crate::config::loader::load_config_from_yaml_file(path)?;
151 Self::start_from_config_state_with_factories(state, task_factory_registry).await
152 }
153
154 pub async fn start_with_factory_registry(
165 spec: SupervisorSpec,
166 task_factory_registry: TaskFactoryRegistry,
167 ) -> Result<SupervisorHandle, SupervisorError> {
168 spec.validate()?;
169 let backpressure_config = spec.backpressure_config.clone();
170 let (command_sender, command_receiver) = mpsc::channel(spec.control_channel_capacity);
171 let (event_sender, _) = broadcast::channel(spec.event_channel_capacity);
172 let control_plane = RuntimeControlPlane::new();
173 let observability = Arc::new(Mutex::new(ObservabilityPipeline::with_backpressure_config(
174 spec.event_channel_capacity,
175 spec.event_channel_capacity,
176 spec.metrics_enabled,
177 spec.audit_enabled,
178 backpressure_config,
179 )));
180 let state = RuntimeControlState::new_with_factory_registry(
181 spec,
182 command_sender.clone(),
183 observability.clone(),
184 task_factory_registry,
185 )?;
186 let join_handle = tokio::spawn(run_control_loop(
187 state,
188 command_receiver,
189 event_sender.clone(),
190 ));
191 RuntimeWatchdog::publish_started(control_plane.clone(), event_sender.clone());
192 RuntimeWatchdog::spawn(control_plane.clone(), join_handle, event_sender.clone());
193 Ok(SupervisorHandle::new_with_observability(
194 command_sender,
195 event_sender,
196 control_plane,
197 observability,
198 ))
199 }
200}
201
202#[cfg(unix)]
204fn dashboard_startup_error(error: DashboardError) -> SupervisorError {
205 SupervisorError::fatal_config(format!("dashboard IPC startup failed: {error}"))
206}