Skip to main content

mecha10_runtime/
launcher.rs

1//! Launcher service integration for dynamic node launching
2//!
3//! LAB-2212 investigated deleting this module as part of consolidating node-lifecycle runners
4//! onto `mecha10-node-runner`, on the premise that only this module's own tests referenced it.
5//! That premise doesn't hold: `Launcher` is wired into `crate::runtime::Runtime`/`RuntimeBuilder`
6//! as an optional feature (`RuntimeBuilder::with_launcher`, `Runtime::launcher()`, both public
7//! API), not merely exercised by its own unit tests - `packages/runtime/tests/launcher_tests.rs`
8//! is a separate integration-test crate driving that same `Runtime`-level surface. No workspace
9//! package outside `runtime` currently calls `with_launcher`/`Runtime::launcher()`, so the
10//! *feature* is unused end-to-end today, but the module itself is genuinely reachable/compiled
11//! crate-internal code, not orphaned dead code - so it was kept as-is rather than deleted. If this
12//! `Runtime`-level launcher feature is still unwanted, retiring it needs a follow-up that also
13//! removes the `with_launcher`/`launcher()` surface from `runtime.rs` and the integration test
14//! suite, not just this file.
15
16use crate::node::NodeRunner;
17use crate::supervisor::{NodeHandle, Supervisor};
18use anyhow::Result;
19use mecha10_core::rwlock_collections::RwLockMapExt;
20use std::collections::HashMap;
21use std::sync::Arc;
22use tokio::sync::RwLock;
23
24/// Registry for node types that can be dynamically launched
25pub type NodeFactory = Box<dyn Fn() -> Box<dyn NodeRunner> + Send + Sync>;
26
27/// Launcher service for dynamic node management
28pub struct Launcher {
29    supervisor: Arc<Supervisor>,
30    factories: Arc<RwLock<HashMap<String, NodeFactory>>>,
31    running_nodes: Arc<RwLock<HashMap<String, NodeHandle>>>,
32}
33
34impl Launcher {
35    /// Create a new launcher
36    pub fn new(supervisor: Arc<Supervisor>) -> Self {
37        Self {
38            supervisor,
39            factories: Arc::new(RwLock::new(HashMap::new())),
40            running_nodes: Arc::new(RwLock::new(HashMap::new())),
41        }
42    }
43
44    /// Register a node type with a factory function
45    pub async fn register<F>(&self, node_type: String, factory: F)
46    where
47        F: Fn() -> Box<dyn NodeRunner> + Send + Sync + 'static,
48    {
49        let mut factories = self.factories.write().await;
50        factories.insert(node_type, Box::new(factory));
51    }
52
53    /// Launch a node by type name
54    pub async fn launch(&self, node_type: &str) -> Result<String> {
55        let factories = self.factories.read().await;
56        let factory = factories
57            .get(node_type)
58            .ok_or_else(|| anyhow::anyhow!("Unknown node type: {}", node_type))?;
59
60        let node = factory();
61        let node_name = node.name().to_string();
62
63        let running = self.running_nodes.read().await;
64        if running.contains_key(&node_name) {
65            anyhow::bail!("Node already running: {}", node_name);
66        }
67        drop(running);
68
69        let handle = self.supervisor.launch_node(node);
70        let name = handle.name().to_string();
71
72        let mut running = self.running_nodes.write().await;
73        running.insert(name.clone(), handle);
74
75        Ok(name)
76    }
77
78    /// Stop a running node
79    pub async fn stop(&self, node_name: &str) -> Result<()> {
80        let mut running = self.running_nodes.write().await;
81        let handle = running
82            .remove(node_name)
83            .ok_or_else(|| anyhow::anyhow!("Node not running: {}", node_name))?;
84
85        handle.abort();
86        Ok(())
87    }
88
89    /// List all running nodes
90    pub async fn list_running(&self) -> Vec<String> {
91        let running = self.running_nodes.read().await;
92        running.keys().cloned().collect()
93    }
94
95    /// Check if a node is running
96    pub async fn is_running(&self, node_name: &str) -> bool {
97        let running = self.running_nodes.read().await;
98        running.contains_key(node_name)
99    }
100
101    /// Get list of registered node types
102    pub async fn list_types(&self) -> Vec<String> {
103        self.factories.cloned_keys().await
104    }
105}