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}