Skip to main content

mecha10_runtime/
supervisor.rs

1//! Node supervision and lifecycle management
2
3use crate::config::RestartPolicy;
4use crate::health::{HealthChecker, HealthStatus};
5use crate::node::NodeRunner;
6use crate::shutdown::ShutdownHandle;
7use anyhow::Result;
8use mecha10_core::rwlock_collections::RwLockMapExt;
9use std::sync::Arc;
10use tokio::task::JoinHandle;
11
12#[derive(Clone, Debug, PartialEq)]
13pub enum NodeStatus {
14    Starting,
15
16    Running,
17
18    ShuttingDown,
19
20    Stopped,
21
22    Failed { reason: String },
23
24    Restarting { attempt: usize },
25}
26
27pub struct NodeHandle {
28    name: String,
29    task: JoinHandle<()>,
30}
31
32impl NodeHandle {
33    /// Get the node name
34    pub fn name(&self) -> &str {
35        &self.name
36    }
37
38    /// Check if the node task is finished
39    pub fn is_finished(&self) -> bool {
40        self.task.is_finished()
41    }
42
43    /// Abort the node task
44    pub fn abort(&self) {
45        self.task.abort();
46    }
47
48    /// Wait for the node to finish
49    pub async fn wait(self) -> Result<()> {
50        self.task.await?;
51        Ok(())
52    }
53}
54
55pub struct Supervisor {
56    health_checker: Arc<HealthChecker>,
57    shutdown: ShutdownHandle,
58    restart_policy: RestartPolicy,
59    nodes: Arc<tokio::sync::RwLock<std::collections::HashMap<String, NodeHandle>>>,
60}
61
62impl Supervisor {
63    /// Create a new supervisor
64    pub fn new(health_checker: Arc<HealthChecker>, shutdown: ShutdownHandle, restart_policy: RestartPolicy) -> Self {
65        Self {
66            health_checker,
67            shutdown,
68            restart_policy,
69            nodes: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
70        }
71    }
72
73    /// Launch and supervise a node
74    pub fn launch_node(&self, mut node: Box<dyn NodeRunner>) -> NodeHandle {
75        let name = node.name().to_string();
76        let health_checker = self.health_checker.clone();
77        let shutdown = self.shutdown.clone();
78        let restart_policy = self.restart_policy.clone();
79
80        tracing::info!("Launching node: {}", name);
81
82        let task = tokio::spawn(async move {
83            let node_name = node.name().to_string();
84            let mut attempt = 0;
85
86            health_checker.register(node_name.clone(), HealthStatus::Healthy).await;
87
88            loop {
89                tracing::debug!("Starting node: {} (attempt {})", node_name, attempt);
90
91                let mut shutdown_rx = shutdown.subscribe();
92                let run_future = node.run();
93
94                tokio::select! {
95                    result = run_future => {
96                        match result {
97                            Ok(()) => {
98                                tracing::info!("Node {} completed successfully", node_name);
99                                health_checker
100                                    .update(&node_name, HealthStatus::Healthy)
101                                    .await;
102                                break;
103                            }
104                            Err(e) => {
105                                tracing::error!("Node {} failed: {}", node_name, e);
106                                health_checker
107                                    .update(
108                                        &node_name,
109                                        HealthStatus::Unhealthy {
110                                            reason: e.to_string(),
111                                        },
112                                    )
113                                    .await;
114
115                                if restart_policy.should_restart(attempt) {
116                                    let backoff = restart_policy.backoff_duration(attempt);
117                                    tracing::info!(
118                                        "Restarting node {} in {:?} (attempt {})",
119                                        node_name,
120                                        backoff,
121                                        attempt + 1
122                                    );
123                                    tokio::time::sleep(backoff).await;
124                                    attempt += 1;
125                                    continue;
126                                } else {
127                                    tracing::error!(
128                                        "Node {} exhausted restart attempts, giving up",
129                                        node_name
130                                    );
131                                    break;
132                                }
133                            }
134                        }
135                    }
136                    _ = shutdown_rx.recv() => {
137                        tracing::info!("Shutdown signal received, stopping node: {}", node_name);
138                        if let Err(e) = node.shutdown().await {
139                            tracing::error!("Error during node {} shutdown: {}", node_name, e);
140                        }
141                        health_checker
142                            .update(&node_name, HealthStatus::Healthy)
143                            .await;
144                        break;
145                    }
146                }
147            }
148
149            health_checker.unregister(&node_name).await;
150            tracing::debug!("Node {} supervision ended", node_name);
151        });
152
153        NodeHandle { name, task }
154    }
155
156    /// Launch multiple nodes
157    pub fn launch_nodes(&self, nodes: Vec<Box<dyn NodeRunner>>) -> Vec<NodeHandle> {
158        nodes.into_iter().map(|node| self.launch_node(node)).collect()
159    }
160
161    /// Get the health checker
162    pub fn health_checker(&self) -> &Arc<HealthChecker> {
163        &self.health_checker
164    }
165
166    /// Get the shutdown handle
167    pub fn shutdown(&self) -> &ShutdownHandle {
168        &self.shutdown
169    }
170
171    /// Start a node by name (for lifecycle management)
172    ///
173    /// Note: This is a stub implementation for the lifecycle system.
174    /// Full implementation requires node registry or factory pattern.
175    ///
176    /// # Arguments
177    ///
178    /// * `name` - Name of the node to start
179    ///
180    /// # Errors
181    ///
182    /// Returns error if node cannot be found or started
183    #[allow(dead_code)]
184    pub async fn start_node(&self, name: &str) -> Result<()> {
185        tracing::info!("Starting node: {}", name);
186
187        self.health_checker
188            .register(name.to_string(), HealthStatus::Healthy)
189            .await;
190
191        tracing::debug!("Node {} registered (stub)", name);
192        Ok(())
193    }
194
195    /// Stop a node by name (for lifecycle management)
196    ///
197    /// # Arguments
198    ///
199    /// * `name` - Name of the node to stop
200    ///
201    /// # Errors
202    ///
203    /// Returns error if node cannot be found or stopped
204    #[allow(dead_code)]
205    pub async fn stop_node(&self, name: &str) -> Result<()> {
206        tracing::info!("Stopping node: {}", name);
207
208        let mut nodes = self.nodes.write().await;
209
210        if let Some(handle) = nodes.remove(name) {
211            handle.abort();
212
213            self.health_checker.unregister(name).await;
214
215            tracing::info!("Node {} stopped", name);
216            Ok(())
217        } else {
218            self.health_checker.unregister(name).await;
219            tracing::warn!(
220                "Node {} not found in tracked nodes, unregistered from health checker",
221                name
222            );
223            Ok(())
224        }
225    }
226
227    /// Get list of currently running nodes
228    ///
229    /// Returns the names of all nodes being supervised
230    #[allow(dead_code)]
231    pub async fn get_running_nodes(&self) -> Vec<String> {
232        self.nodes.cloned_keys().await
233    }
234}
235
236#[async_trait::async_trait]
237impl crate::lifecycle::SupervisorTrait for Supervisor {
238    async fn start_node(&self, name: &str) -> Result<()> {
239        self.start_node(name).await
240    }
241
242    async fn stop_node(&self, name: &str) -> Result<()> {
243        self.stop_node(name).await
244    }
245
246    fn get_running_nodes(&self) -> Vec<String> {
247        vec![]
248    }
249}