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
24pub type NodeFactory = Box<dyn Fn() -> Box<dyn NodeRunner> + Send + Sync>;
25
26pub struct Launcher {
27    supervisor: Arc<Supervisor>,
28    factories: Arc<RwLock<HashMap<String, NodeFactory>>>,
29    running_nodes: Arc<RwLock<HashMap<String, NodeHandle>>>,
30}
31
32impl Launcher {
33    /// Create a new launcher
34    pub fn new(supervisor: Arc<Supervisor>) -> Self {
35        Self {
36            supervisor,
37            factories: Arc::new(RwLock::new(HashMap::new())),
38            running_nodes: Arc::new(RwLock::new(HashMap::new())),
39        }
40    }
41
42    /// Register a node type with a factory function
43    pub async fn register<F>(&self, node_type: String, factory: F)
44    where
45        F: Fn() -> Box<dyn NodeRunner> + Send + Sync + 'static,
46    {
47        let mut factories = self.factories.write().await;
48        factories.insert(node_type, Box::new(factory));
49    }
50
51    /// Launch a node by type name
52    pub async fn launch(&self, node_type: &str) -> Result<String> {
53        let factories = self.factories.read().await;
54        let factory = factories
55            .get(node_type)
56            .ok_or_else(|| anyhow::anyhow!("Unknown node type: {}", node_type))?;
57
58        let node = factory();
59        let node_name = node.name().to_string();
60
61        let running = self.running_nodes.read().await;
62        if running.contains_key(&node_name) {
63            anyhow::bail!("Node already running: {}", node_name);
64        }
65        drop(running);
66
67        let handle = self.supervisor.launch_node(node);
68        let name = handle.name().to_string();
69
70        let mut running = self.running_nodes.write().await;
71        running.insert(name.clone(), handle);
72
73        Ok(name)
74    }
75
76    /// Stop a running node
77    pub async fn stop(&self, node_name: &str) -> Result<()> {
78        let mut running = self.running_nodes.write().await;
79        let handle = running
80            .remove(node_name)
81            .ok_or_else(|| anyhow::anyhow!("Node not running: {}", node_name))?;
82
83        handle.abort();
84        Ok(())
85    }
86
87    /// List all running nodes
88    pub async fn list_running(&self) -> Vec<String> {
89        let running = self.running_nodes.read().await;
90        running.keys().cloned().collect()
91    }
92
93    /// Check if a node is running
94    pub async fn is_running(&self, node_name: &str) -> bool {
95        let running = self.running_nodes.read().await;
96        running.contains_key(node_name)
97    }
98
99    /// Get list of registered node types
100    pub async fn list_types(&self) -> Vec<String> {
101        self.factories.cloned_keys().await
102    }
103}