mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
debugger: Fix restart only working once per session (#51247)
`Session::restart_task` is set to `Some` when a restart is initiated but never cleared back to `None`. The guard at the top of `restart()` checks `self.restart_task.is_some()` and returns early, so only the first restart attempt succeeds. This primarily affects debug adapters that advertise `supportsRestartRequest` dynamically via a `CapabilitiesEvent` after launch, such as the Flutter debug adapter. Related: https://github.com/zed-extensions/dart/issues/45 Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [ ] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) (N/A — no UI changes) Release Notes: - debugger: Fixed debug session restart only working once when the adapter supports DAP restart requests. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
parent
e0881e38f9
commit
314b7e55fb
3 changed files with 97 additions and 13 deletions
|
|
@ -132,7 +132,13 @@ pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
|||
.workspace()
|
||||
.read(cx)
|
||||
.panel::<DebugPanel>(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")
|
||||
|
|
|
|||
|
|
@ -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::<dap::requests::Initialize, _>(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::<dap::requests::Restart, _>({
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue