debugger: Don't spin forever when adapter disconnects unexpectedly (#32489)

Cole Miller , Anthony Eid , and Conrad Irwin created

Closes #ISSUE

Release Notes:

- Debugger Beta: made the debug panel UI more helpful when an invalid
configuration is sent to the debug adapter.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/dap/src/transport.rs            | 10 +++++++-
crates/project/src/debugger/session.rs | 30 ++++++++++++++++++++++-----
2 files changed, 32 insertions(+), 8 deletions(-)

Detailed changes

crates/dap/src/transport.rs 🔗

@@ -1,4 +1,4 @@
-use anyhow::{Context as _, Result, bail};
+use anyhow::{Context as _, Result, anyhow, bail};
 use dap_types::{
     ErrorResponse,
     messages::{Message, Response},
@@ -191,7 +191,7 @@ impl TransportDelegate {
                 match Self::handle_output(
                     params.output,
                     client_tx,
-                    pending_requests,
+                    pending_requests.clone(),
                     output_log_handler,
                 )
                 .await
@@ -199,6 +199,12 @@ impl TransportDelegate {
                     Ok(()) => {}
                     Err(e) => log::error!("Error handling debugger output: {e}"),
                 }
+                let mut pending_requests = pending_requests.lock().await;
+                pending_requests.drain().for_each(|(_, request)| {
+                    request
+                        .send(Err(anyhow!("debugger shutdown unexpectedly")))
+                        .ok();
+                });
             }));
 
             if let Some(stderr) = params.stderr.take() {

crates/project/src/debugger/session.rs 🔗

@@ -27,6 +27,7 @@ use dap::{
     ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEvent, OutputEventCategory,
     RunInTerminalRequestArguments, StartDebuggingRequestArguments,
 };
+use futures::SinkExt;
 use futures::channel::{mpsc, oneshot};
 use futures::{FutureExt, future::Shared};
 use gpui::{
@@ -458,7 +459,7 @@ impl RunningMode {
         let task = cx.background_spawn(futures::future::try_join(launch, configuration_sequence));
 
         cx.spawn(async move |this, cx| {
-            task.await?;
+            let result = task.await;
 
             this.update(cx, |this, cx| {
                 if let Some(this) = this.as_running_mut() {
@@ -468,6 +469,7 @@ impl RunningMode {
             })
             .ok();
 
+            result?;
             anyhow::Ok(())
         })
     }
@@ -823,7 +825,7 @@ impl Session {
                 id,
                 parent_session,
                 worktree.downgrade(),
-                binary,
+                binary.clone(),
                 message_tx,
                 cx.clone(),
             )
@@ -836,10 +838,26 @@ impl Session {
             this.update(cx, |session, cx| session.request_initialize(cx))?
                 .await?;
 
-            this.update(cx, |session, cx| {
-                session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
-            })?
-            .await
+            let result = this
+                .update(cx, |session, cx| {
+                    session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
+                })?
+                .await;
+
+            if result.is_err() {
+                let mut console = this.update(cx, |session, cx| session.console_output(cx))?;
+
+                console
+                    .send(format!(
+                        "Tried to launch debugger with: {}",
+                        serde_json::to_string_pretty(&binary.request_args.configuration)
+                            .unwrap_or_default(),
+                    ))
+                    .await
+                    .ok();
+            }
+
+            result
         })
     }