Skip to main content

dscode_dap/
pool.rs

1/**
2 * Debug Adapter Pool
3 *
4 * Manages multiple debug adapter instances with:
5 * - One adapter per debug session
6 * - Session lifecycle management
7 * - Request routing
8 */
9use crate::adapter::DebugAdapter;
10use crate::types::{DebugSession, DebugState};
11use std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use tracing::info;
15
16type AdapterConfigMap = Arc<RwLock<HashMap<String, (String, Vec<String>)>>>;
17
18/// Information about a running debug adapter
19pub(crate) struct DebugAdapterInfo {
20    #[allow(dead_code)]
21    pub(crate) session_id: String,
22    pub(crate) adapter: Arc<DebugAdapter>,
23    pub(crate) state: DebugState,
24}
25
26/// Pool for managing multiple debug adapters
27pub struct DebugAdapterPool {
28    /// All running debug adapters
29    adapters: Arc<RwLock<HashMap<String, DebugAdapterInfo>>>,
30
31    /// Adapter configurations: adapter_type -> (command, args)
32    configurations: AdapterConfigMap,
33}
34
35impl DebugAdapterPool {
36    pub fn new() -> Self {
37        Self {
38            adapters: Arc::new(RwLock::new(HashMap::new())),
39            configurations: Arc::new(RwLock::new(HashMap::new())),
40        }
41    }
42
43    /// Register a debug adapter configuration
44    pub async fn register_adapter(
45        &self, adapter_type: String, adapter_command: String, adapter_args: Vec<String>,
46    ) {
47        let mut configs = self.configurations.write().await;
48        configs.insert(adapter_type.clone(), (adapter_command, adapter_args));
49        info!(adapter_type = %adapter_type, "Registered adapter configuration");
50    }
51
52    /// Create and start a new debug session
53    pub async fn create_session(&self, session: DebugSession) -> Result<Arc<DebugAdapter>, String> {
54        // Get adapter configuration
55        let (command, args) = {
56            let configs = self.configurations.read().await;
57            configs
58                .get(&session.adapter_type)
59                .ok_or(format!("No configuration found for adapter type {}", session.adapter_type))?
60                .clone()
61        };
62
63        // Create adapter
64        let adapter = Arc::new(DebugAdapter::new(session.clone(), command, args));
65
66        // Start the adapter
67        adapter.start().await?;
68
69        // Add to pool
70        let adapter_info = DebugAdapterInfo {
71            session_id: session.id.clone(),
72            adapter: Arc::clone(&adapter),
73            state: DebugState::Stopped,
74        };
75
76        let mut adapters = self.adapters.write().await;
77        adapters.insert(session.id.clone(), adapter_info);
78
79        info!(id = %session.id, "Started adapter for session");
80        Ok(adapter)
81    }
82
83    /// Get an existing debug adapter
84    pub async fn get_adapter(&self, session_id: &str) -> Option<Arc<DebugAdapter>> {
85        let adapters = self.adapters.read().await;
86        adapters.get(session_id).map(|info| Arc::clone(&info.adapter))
87    }
88
89    /// Update session state
90    pub async fn update_state(&self, session_id: &str, state: DebugState) -> Result<(), String> {
91        let mut adapters = self.adapters.write().await;
92        if let Some(info) = adapters.get_mut(session_id) {
93            info.state = state;
94            Ok(())
95        } else {
96            Err(format!("Session {} not found", session_id))
97        }
98    }
99
100    /// Stop a debug session
101    pub async fn stop_session(&self, session_id: &str) -> Result<(), String> {
102        let mut adapters = self.adapters.write().await;
103
104        if let Some(adapter_info) = adapters.remove(session_id) {
105            adapter_info.adapter.stop().await?;
106            info!(id = %session_id, "Stopped debug session");
107            Ok(())
108        } else {
109            Err(format!("Session {} not found", session_id))
110        }
111    }
112
113    pub async fn stop_all(&self) -> Result<(), String> {
114        let mut adapters = self.adapters.write().await;
115
116        for (session_id, adapter_info) in adapters.iter() {
117            adapter_info.adapter.stop().await?;
118            info!(id = %session_id, "Stopped debug session");
119        }
120
121        adapters.clear();
122        Ok(())
123    }
124
125    /// Get list of active sessions
126    pub async fn list_sessions(&self) -> Vec<String> {
127        let adapters = self.adapters.read().await;
128        adapters.keys().cloned().collect()
129    }
130
131    /// Get statistics
132    pub async fn get_stats(&self) -> DebugPoolStats {
133        let adapters = self.adapters.read().await;
134        let total_sessions = adapters.len();
135
136        let mut states: HashMap<DebugState, usize> = HashMap::new();
137        for info in adapters.values() {
138            *states.entry(info.state.clone()).or_insert(0) += 1;
139        }
140
141        DebugPoolStats { total_sessions, states }
142    }
143}
144
145impl Default for DebugAdapterPool {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151#[derive(Debug, Clone)]
152pub struct DebugPoolStats {
153    pub total_sessions: usize,
154    pub states: HashMap<DebugState, usize>,
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[tokio::test]
162    async fn test_pool_new() {
163        let pool = DebugAdapterPool::new();
164        let sessions = pool.list_sessions().await;
165        assert!(sessions.is_empty(), "New pool should have no sessions");
166    }
167
168    #[tokio::test]
169    async fn test_pool_stats_empty() {
170        let pool = DebugAdapterPool::new();
171        let stats = pool.get_stats().await;
172        assert_eq!(stats.total_sessions, 0, "Empty pool should have 0 sessions");
173        assert!(stats.states.is_empty(), "Empty pool should have no state counts");
174    }
175
176    #[tokio::test]
177    async fn test_pool_register_adapter() {
178        let pool = DebugAdapterPool::new();
179        pool.register_adapter("cppdbg".to_string(), "/usr/bin/gdb".to_string(), vec![])
180            .await;
181
182        // Registering a config does not create a running session
183        let sessions = pool.list_sessions().await;
184        assert!(sessions.is_empty(), "Registering config should not create a session");
185
186        let stats = pool.get_stats().await;
187        assert_eq!(stats.total_sessions, 0);
188    }
189
190    #[tokio::test]
191    async fn test_pool_get_adapter_nonexistent() {
192        let pool = DebugAdapterPool::new();
193        let result = pool.get_adapter("nonexistent-session").await;
194        assert!(result.is_none(), "Getting nonexistent adapter should return None");
195    }
196
197    #[tokio::test]
198    async fn test_pool_create_session_unconfigured() {
199        let pool = DebugAdapterPool::new();
200        let session = DebugSession {
201            id: "test-session".to_string(),
202            name: "Test".to_string(),
203            state: DebugState::Stopped,
204            adapter_type: "nonexistent".to_string(),
205        };
206        let result = pool.create_session(session).await;
207        assert!(result.is_err(), "Should fail when no adapter config registered");
208        assert!(result.unwrap_err().contains("No configuration found"));
209    }
210
211    #[tokio::test]
212    async fn test_pool_stop_session_nonexistent() {
213        let pool = DebugAdapterPool::new();
214        let result = pool.stop_session("nonexistent").await;
215        assert!(result.is_err());
216        assert!(result.unwrap_err().contains("not found"));
217    }
218
219    #[tokio::test]
220    async fn test_pool_update_state_nonexistent() {
221        let pool = DebugAdapterPool::new();
222        let result = pool.update_state("nonexistent", DebugState::Running).await;
223        assert!(result.is_err());
224        assert!(result.unwrap_err().contains("not found"));
225    }
226
227    #[tokio::test]
228    async fn test_pool_register_multiple_configs() {
229        let pool = DebugAdapterPool::new();
230        pool.register_adapter("cppdbg".to_string(), "/usr/bin/gdb".to_string(), vec![])
231            .await;
232        pool.register_adapter("python".to_string(), "debugpy".to_string(), vec!["--listen".to_string()])
233            .await;
234        pool.register_adapter("go".to_string(), "dlv".to_string(), vec![])
235            .await;
236
237        // Configs registered but no sessions started
238        assert!(pool.list_sessions().await.is_empty());
239    }
240
241    #[tokio::test]
242    async fn test_pool_list_sessions_empty() {
243        let pool = DebugAdapterPool::new();
244        let sessions = pool.list_sessions().await;
245        assert!(sessions.is_empty());
246    }
247
248    #[tokio::test]
249    async fn test_pool_default_trait() {
250        let pool = DebugAdapterPool::default();
251        assert!(pool.list_sessions().await.is_empty());
252    }
253}