mecha10_core/context/
lifecycle.rs1use super::Context;
4#[cfg(feature = "messaging")]
5use crate::error::Mecha10Error;
6use crate::error::Result;
7#[cfg(feature = "messaging")]
8use anyhow::Context as _;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12impl Context {
13 pub async fn new(node_id: &str) -> Result<Self> {
34 let node_id = std::env::var("NODE_NAME").unwrap_or_else(|_| node_id.to_string());
35 #[cfg(feature = "messaging")]
36 let (message_bus, control_plane_bus, redis_bridge) = {
37 let config = Self::load_infrastructure_config()?;
38
39 let redis_url = Self::get_redis_url_from_config(&config)?;
40 let local_bus = mecha10_messaging::MessageBus::connect(&redis_url, &node_id)
41 .await
42 .with_context(|| {
43 format!(
44 "Failed to connect local message bus for node '{}' to Redis at '{}'",
45 node_id, redis_url
46 )
47 })
48 .map_err(|e| Mecha10Error::MessagingError {
49 message: format!("{:#}", e),
50 suggestion: "Ensure local Redis is running at the configured URL".to_string(),
51 })?;
52
53 let cp_bus = if let Some(cp_config) = &config.control_plane_redis {
54 let cp_url = &cp_config.url;
55 match mecha10_messaging::MessageBus::connect(cp_url, &node_id).await {
56 Ok(bus) => {
57 tracing::info!(
58 "Connected to control plane Redis at '{}' for node '{}'",
59 cp_url,
60 node_id
61 );
62 Some(bus)
63 }
64 Err(e) => {
65 tracing::warn!(
66 "Failed to connect to control plane Redis at '{}': {}. Continuing with local Redis only.",
67 cp_url,
68 e
69 );
70 None
71 }
72 }
73 } else {
74 None
75 };
76
77 let bridge = if let Some(_cp_bus_ref) = &cp_bus {
78 let bridge_config = config.bridge.unwrap_or_default();
79
80 if bridge_config.enabled {
81 let bridge_local_bus = mecha10_messaging::MessageBus::connect(&redis_url, &node_id)
82 .await
83 .map_err(|e| Mecha10Error::MessagingError {
84 message: format!("Failed to create bridge connection to local Redis: {}", e),
85 suggestion: "Check local Redis connection".to_string(),
86 })?;
87
88 let cp_url = config
89 .control_plane_redis
90 .as_ref()
91 .map(|c| c.url.clone())
92 .unwrap_or_default();
93 let bridge_cp_bus = mecha10_messaging::MessageBus::connect(&cp_url, &node_id)
94 .await
95 .map_err(|e| Mecha10Error::MessagingError {
96 message: format!("Failed to create bridge connection to control plane Redis: {}", e),
97 suggestion: "Check control plane Redis connection".to_string(),
98 })?;
99
100 let redis_bridge_config = crate::redis_bridge::BridgeConfig {
101 enabled: bridge_config.enabled,
102 local_topics: bridge_config.local_topics,
103 bridged_topics: bridge_config.bridged_topics,
104 robot_id: bridge_config.robot_id,
105 };
106
107 let bridge =
108 crate::redis_bridge::RedisBridge::new(redis_bridge_config, bridge_local_bus, bridge_cp_bus);
109
110 if let Err(e) = bridge.start().await {
111 tracing::warn!("Failed to start Redis bridge: {}. Continuing without bridge.", e);
112 None
113 } else {
114 tracing::info!("Redis bridge started successfully");
115 Some(bridge)
116 }
117 } else {
118 tracing::debug!("Redis bridge is disabled in configuration");
119 None
120 }
121 } else {
122 None
123 };
124
125 (local_bus, cp_bus, bridge)
126 };
127
128 Ok(Self {
129 node_id,
130 instance: None,
131 #[cfg(feature = "messaging")]
132 message_bus: Arc::new(RwLock::new(message_bus)),
133 #[cfg(feature = "messaging")]
134 control_plane_bus: Arc::new(RwLock::new(control_plane_bus)),
135 #[cfg(feature = "messaging")]
136 redis_bridge: Arc::new(RwLock::new(redis_bridge)),
137 shutdown: Arc::new(RwLock::new(false)),
138 state_manager: Arc::new(RwLock::new(None)),
139 })
140 }
141
142 pub fn with_instance(mut self, instance: &str) -> Self {
165 self.instance = Some(instance.to_string());
166 self.node_id = format!("{}/{}", self.node_id, instance);
167 self
168 }
169
170 pub fn instance(&self) -> Option<&str> {
175 self.instance.as_deref()
176 }
177
178 pub fn node_id(&self) -> &str {
180 &self.node_id
181 }
182
183 #[cfg(feature = "messaging")]
185 pub async fn has_control_plane(&self) -> bool {
186 self.control_plane_bus.read().await.is_some()
187 }
188
189 pub async fn is_shutdown(&self) -> bool {
191 *self.shutdown.read().await
192 }
193
194 pub async fn shutdown(&self) {
196 *self.shutdown.write().await = true;
197
198 #[cfg(feature = "messaging")]
199 {
200 let bridge_opt = self.redis_bridge.read().await;
201 if let Some(bridge) = bridge_opt.as_ref() {
202 if let Err(e) = bridge.stop().await {
203 tracing::warn!("Failed to stop Redis bridge during shutdown: {}", e);
204 }
205 }
206 }
207 }
208
209 pub async fn wait_for_shutdown(&self) {
211 loop {
212 if self.is_shutdown().await {
213 break;
214 }
215 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
216 }
217 }
218
219 pub fn logging_span(&self) -> tracing::Span {
237 tracing::info_span!("node", node_id = %self.node_id)
238 }
239}