Skip to main content

Mountain/ApplicationState/State/FeatureState/Debug/
DebugState.rs

1//! # DebugState Module (ApplicationState)
2
3//! ## RESPONSIBILITIES
4//! Manages debug provider state including debug configuration providers and
5//! adapter descriptor factories.
6
7//! ## ARCHITECTURAL ROLE
8//! DebugState is part of the **FeatureState** module, storing debug provider
9//! registrations keyed by debug type.
10
11//! ## KEY COMPONENTS
12//! - DebugState: Main struct containing debug provider registrations
13//! - Default: Initialization implementation
14//! - Helper methods: Debug registration management
15
16//! ## ERROR HANDLING
17//! - Thread-safe access via `Arc<tokio::sync::RwLock<...>>`
18//! - Proper lock error handling
19
20//! ## LOGGING
21//! State changes are logged at appropriate levels (debug, info, warn, error).
22
23//! ## PERFORMANCE CONSIDERATIONS
24//! - Lock mutexes briefly and release immediately
25//! - Use Arc for shared ownership across threads
26
27use std::{
28	collections::HashMap,
29	sync::{Arc, Mutex as StandardMutex},
30};
31
32use crate::dev_log;
33
34/// Debug configuration provider registration info
35#[derive(Clone, Debug)]
36pub struct DebugConfigurationProviderRegistration {
37	/// The provider handle
38	pub ProviderHandle:u32,
39
40	/// The sidecar identifier hosting this provider
41	pub SideCarIdentifier:String,
42}
43
44/// Debug adapter descriptor factory registration info
45#[derive(Clone, Debug)]
46pub struct DebugAdapterDescriptorFactoryRegistration {
47	/// The factory handle
48	pub FactoryHandle:u32,
49
50	/// The sidecar identifier hosting this factory
51	pub SideCarIdentifier:String,
52}
53
54/// Active debug session entry. Lives in the `DebugSessions` map keyed by
55/// session-id (`Uuid::new_v4()` allocated by `DebugProvider::StartDebugging`)
56/// so subsequent `SendCommand` calls can resolve the writer end of the
57/// spawned adapter's stdin pipe and `DisposeSession` can kill the process.
58///
59/// `StdinSender` is `None` for debug-types whose adapter descriptor wasn't
60/// of the executable kind we know how to spawn (TCP `server` descriptors,
61/// `inlineImplementation` descriptors handled entirely in Cocoon, etc.).
62/// In those cases we still record the session so command routing can fall
63/// through to a reverse-RPC into Cocoon instead of dropping silently.
64#[derive(Clone)]
65pub struct DebugSessionEntry {
66	/// Session ID assigned at `StartDebugging` time.
67	pub SessionId:String,
68
69	/// Debug type (e.g. `"node"`, `"chrome"`) - mirrors the configuration
70	/// `type` field, used for diagnostics and routing.
71	pub DebugType:String,
72
73	/// Sidecar that owns the configuration-provider / adapter-descriptor
74	/// factory. Used for reverse-RPC dispatch when the adapter is not a
75	/// spawned executable.
76	pub SideCarIdentifier:String,
77
78	/// Channel that writes raw DAP frame bytes to the adapter's stdin.
79	/// `None` for non-executable adapter kinds.
80	pub StdinSender:Option<tokio::sync::mpsc::UnboundedSender<Vec<u8>>>,
81
82	/// PID of the spawned adapter process (when applicable). `None` for
83	/// non-executable kinds. Mountain doesn't keep a live `Child` handle
84	/// here because `Child` isn't `Clone`; the process termination is
85	/// signalled via dropping `StdinSender`, which the spawn's
86	/// stdout/stderr drain tasks treat as shutdown.
87	pub ChildPid:Option<u32>,
88}
89
90/// Debug state containing debug provider registrations.
91#[derive(Clone)]
92pub struct DebugState {
93	/// Debug configuration providers organized by debug type.
94	pub DebugConfigurationProviders:Arc<StandardMutex<HashMap<String, DebugConfigurationProviderRegistration>>>,
95
96	/// Debug adapter descriptor factories organized by debug type.
97	pub DebugAdapterDescriptorFactories:Arc<StandardMutex<HashMap<String, DebugAdapterDescriptorFactoryRegistration>>>,
98
99	/// Active debug sessions indexed by session-id. Populated by
100	/// `DebugProvider::StartDebugging` after the adapter is resolved
101	/// (and optionally spawned); removed by `DebugProvider::StopDebugging`
102	/// or when the adapter exits. `SendCommand` reads this map to find
103	/// the writer for the targeted session.
104	pub DebugSessions:Arc<StandardMutex<HashMap<String, DebugSessionEntry>>>,
105}
106
107impl Default for DebugState {
108	fn default() -> Self {
109		dev_log!("exthost", "[DebugState] Initializing default debug state...");
110
111		Self {
112			DebugConfigurationProviders:Arc::new(StandardMutex::new(HashMap::new())),
113
114			DebugAdapterDescriptorFactories:Arc::new(StandardMutex::new(HashMap::new())),
115
116			DebugSessions:Arc::new(StandardMutex::new(HashMap::new())),
117		}
118	}
119}
120
121impl DebugState {
122	/// Registers a debug configuration provider.
123	pub fn RegisterDebugConfigurationProvider(
124		&self,
125
126		debug_type:String,
127
128		provider_handle:u32,
129
130		sidecar_identifier:String,
131	) -> Result<(), String> {
132		let mut guard = self
133			.DebugConfigurationProviders
134			.lock()
135			.map_err(|e| format!("Failed to lock debug configuration providers: {}", e))?;
136
137		guard.insert(
138			debug_type,
139			DebugConfigurationProviderRegistration {
140				ProviderHandle:provider_handle,
141				SideCarIdentifier:sidecar_identifier,
142			},
143		);
144
145		Ok(())
146	}
147
148	/// Gets a debug configuration provider registration by debug type.
149	pub fn GetDebugConfigurationProvider(&self, debug_type:&str) -> Option<DebugConfigurationProviderRegistration> {
150		self.DebugConfigurationProviders
151			.lock()
152			.ok()
153			.and_then(|guard| guard.get(debug_type).cloned())
154	}
155
156	/// Registers a debug adapter descriptor factory.
157	pub fn RegisterDebugAdapterDescriptorFactory(
158		&self,
159
160		debug_type:String,
161
162		factory_handle:u32,
163
164		sidecar_identifier:String,
165	) -> Result<(), String> {
166		let mut guard = self
167			.DebugAdapterDescriptorFactories
168			.lock()
169			.map_err(|e| format!("Failed to lock debug adapter descriptor factories: {}", e))?;
170
171		guard.insert(
172			debug_type,
173			DebugAdapterDescriptorFactoryRegistration {
174				FactoryHandle:factory_handle,
175				SideCarIdentifier:sidecar_identifier,
176			},
177		);
178
179		Ok(())
180	}
181
182	/// Gets a debug adapter descriptor factory registration by debug type.
183	pub fn GetDebugAdapterDescriptorFactory(
184		&self,
185
186		debug_type:&str,
187	) -> Option<DebugAdapterDescriptorFactoryRegistration> {
188		self.DebugAdapterDescriptorFactories
189			.lock()
190			.ok()
191			.and_then(|guard| guard.get(debug_type).cloned())
192	}
193
194	/// Gets all registered debug configuration providers.
195	pub fn GetAllDebugConfigurationProviders(&self) -> HashMap<String, DebugConfigurationProviderRegistration> {
196		self.DebugConfigurationProviders
197			.lock()
198			.ok()
199			.map(|guard| guard.clone())
200			.unwrap_or_default()
201	}
202
203	/// Gets all registered debug adapter descriptor factories.
204	pub fn GetAllDebugAdapterDescriptorFactories(&self) -> HashMap<String, DebugAdapterDescriptorFactoryRegistration> {
205		self.DebugAdapterDescriptorFactories
206			.lock()
207			.ok()
208			.map(|guard| guard.clone())
209			.unwrap_or_default()
210	}
211
212	/// Records an active debug session. Replaces any prior entry under the
213	/// same `SessionId` (defensive: shouldn't happen since IDs are uuids).
214	pub fn RegisterDebugSession(&self, Entry:DebugSessionEntry) -> Result<(), String> {
215		let mut Guard = self
216			.DebugSessions
217			.lock()
218			.map_err(|Error| format!("Failed to lock DebugSessions: {}", Error))?;
219
220		Guard.insert(Entry.SessionId.clone(), Entry);
221
222		Ok(())
223	}
224
225	/// Resolves an active session by id. Returns a `Clone` so the caller
226	/// can drop the lock before doing IO with the entry's `StdinSender`.
227	pub fn GetDebugSession(&self, SessionId:&str) -> Option<DebugSessionEntry> {
228		self.DebugSessions.lock().ok().and_then(|Guard| Guard.get(SessionId).cloned())
229	}
230
231	/// Removes a session from the registry. Dropping the returned entry's
232	/// `StdinSender` triggers the adapter-spawn drain tasks to wind down
233	/// (their `recv()` returns `None`) which closes the adapter stdin and
234	/// the adapter shuts itself down.
235	pub fn UnregisterDebugSession(&self, SessionId:&str) -> Option<DebugSessionEntry> {
236		self.DebugSessions.lock().ok().and_then(|mut Guard| Guard.remove(SessionId))
237	}
238
239	/// Snapshot of all active sessions. Used by diagnostic dev_log surfaces
240	/// and the reverse-RPC dispatch when no session-id is supplied.
241	pub fn GetAllDebugSessions(&self) -> HashMap<String, DebugSessionEntry> {
242		self.DebugSessions.lock().ok().map(|Guard| Guard.clone()).unwrap_or_default()
243	}
244}