diff --git a/src/daemon/session.rs b/src/daemon/session.rs index 5bd5b12..3eef018 100644 --- a/src/daemon/session.rs +++ b/src/daemon/session.rs @@ -225,17 +225,11 @@ impl DebugSession { "Sending DAP launch request" ); - // For Python/debugpy, we need to use non-blocking launch because debugpy - // doesn't respond to launch until after configurationDone is sent. - // We send launch, wait for initialized, send configurationDone, then - // the launch response arrives. - if is_python { - client.launch_no_wait(launch_args).await?; - tracing::debug!("DAP launch request sent (no-wait mode for Python)"); - } else { - client.launch(launch_args).await?; - tracing::debug!("DAP launch request successful"); - } + // The DAP specification allows adapters to defer the launch response + // until after configurationDone is received. To prevent deadlocks with + // strict adapters (like debugpy and lldb-dap), we use a non-blocking launch. + client.launch_no_wait(launch_args).await?; + tracing::debug!("DAP launch request sent (no-wait mode)"); // Wait for initialized event (comes after launch per DAP spec) tracing::debug!(timeout_secs = request_timeout.as_secs(), "Waiting for DAP initialized event"); diff --git a/src/dap/client.rs b/src/dap/client.rs index 75449b1..046c057 100644 --- a/src/dap/client.rs +++ b/src/dap/client.rs @@ -706,7 +706,53 @@ impl DapClient { /// configurationDone is sent. This sends the launch request but doesn't /// wait for the response. pub async fn launch_no_wait(&mut self, args: LaunchArguments) -> Result { - self.send_request("launch", Some(serde_json::to_value(&args)?)).await + let seq = self.next_seq(); + let (tx, rx) = tokio::sync::oneshot::channel(); + { + let mut pending_guard = self.pending.lock().await; + pending_guard.insert(seq, tx); + } + + let request = serde_json::json!({ + "seq": seq, + "type": "request", + "command": "launch", + "arguments": serde_json::to_value(&args)? + }); + + let json = serde_json::to_string(&request)?; + tracing::trace!("DAP >>> {}", json); + + if let Err(e) = codec::write_message(&mut self.writer, &json).await { + let mut pending_guard = self.pending.lock().await; + pending_guard.remove(&seq); + return Err(e); + } + + // Spawn a background task to await the response and log any failures + tokio::spawn(async move { + if let Ok(inner_result) = rx.await { + match inner_result { + Ok(response) => { + if !response.success { + tracing::error!( + "Launch request failed: {}", + response.message.unwrap_or_else(|| "Unknown error".to_string()) + ); + } else { + tracing::debug!("Launch request succeeded (async)"); + } + } + Err(e) => { + tracing::error!("Launch request resulted in an error: {}", e); + } + } + } else { + tracing::debug!("Launch response channel closed before receiving a reply"); + } + }); + + Ok(seq) } /// Attach to a running process