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