diff --git a/crates/debugger_ui/src/tests.rs b/crates/debugger_ui/src/tests.rs index c183f8941c3f30cb43ffaa638eae4e6b387e226d..cc407dfd810ceedb11c4d8030c46a6f17065b34b 100644 --- a/crates/debugger_ui/src/tests.rs +++ b/crates/debugger_ui/src/tests.rs @@ -132,7 +132,13 @@ pub fn start_debug_session_with) + 'static>( .workspace() .read(cx) .panel::(cx) - .and_then(|panel| panel.read(cx).active_session()) + .and_then(|panel| { + panel + .read(cx) + .sessions_with_children + .keys() + .max_by_key(|session| session.read(cx).session_id(cx)) + }) .map(|session| session.read(cx).running_state().read(cx).session()) .cloned() .context("Failed to get active session") diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 207e82b4958941e04ea04fc47c9471141e61a64d..e4c258a8d2af0b865f13c28430c44a66117a11cd 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -27,7 +27,7 @@ use std::{ path::Path, sync::{ Arc, - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicUsize, Ordering}, }, }; use terminal_view::terminal_panel::TerminalPanel; @@ -2481,3 +2481,75 @@ async fn test_adapter_shutdown_with_child_sessions_on_app_quit( "Child session should have received disconnect request" ); } + +#[gpui::test] +async fn test_restart_request_is_not_sent_more_than_once_until_response( + executor: BackgroundExecutor, + cx: &mut TestAppContext, +) { + init_test(cx); + + let fs = FakeFs::new(executor.clone()); + + fs.insert_tree( + path!("/project"), + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, [path!("/project").as_ref()], cx).await; + let workspace = init_test_workspace(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + + let session = start_debug_session(&workspace, cx, move |client| { + client.on_request::(move |_, _| { + Ok(dap::Capabilities { + supports_restart_request: Some(true), + ..Default::default() + }) + }); + }) + .unwrap(); + + let client = session.update(cx, |session, _| session.adapter_client().unwrap()); + + let restart_count = Arc::new(AtomicUsize::new(0)); + + client.on_request::({ + let restart_count = restart_count.clone(); + move |_, _| { + restart_count.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + }); + + // This works because the restart request sender is on the foreground thread + // so it will start running after the gpui update stack is cleared + session.update(cx, |session, cx| { + session.restart(None, cx); + session.restart(None, cx); + session.restart(None, cx); + }); + + cx.run_until_parked(); + + assert_eq!( + restart_count.load(Ordering::SeqCst), + 1, + "Only one restart request should be sent while a restart is in-flight" + ); + + session.update(cx, |session, cx| { + session.restart(None, cx); + }); + + cx.run_until_parked(); + + assert_eq!( + restart_count.load(Ordering::SeqCst), + 2, + "A second restart should be allowed after the first one completes" + ); +} diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index a6c3f52b17a4a6cf241aa49329f3f14f0b5cefbc..87e11cfd97a2f63bba3cefca671e4413deb6765f 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -2187,21 +2187,27 @@ impl Session { self.capabilities.supports_restart_request.unwrap_or(false) && !self.is_terminated(); self.restart_task = Some(cx.spawn(async move |this, cx| { - let _ = this.update(cx, |session, cx| { + this.update(cx, |session, cx| { if supports_dap_restart { - session - .request( - RestartCommand { - raw: args.unwrap_or(Value::Null), - }, - Self::fallback_to_manual_restart, - cx, - ) - .detach(); + session.request( + RestartCommand { + raw: args.unwrap_or(Value::Null), + }, + Self::fallback_to_manual_restart, + cx, + ) } else { cx.emit(SessionStateEvent::Restart); + Task::ready(None) } - }); + }) + .unwrap_or_else(|_| Task::ready(None)) + .await; + + this.update(cx, |session, _cx| { + session.restart_task = None; + }) + .ok(); })); }