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}