From b6dfde07bf5de420fef3a9b6d28fac49bd3577ff Mon Sep 17 00:00:00 2001 From: arookieofc <2128194521@qq.com> Date: Fri, 6 Nov 2026 21:52:15 +0800 Subject: [PATCH] fix(go): sweep Delve's leftover __debug_bin files when a session ends (#1829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delve compiles the debuggee to __debug_bin in the session's cwd (on Windows __debug_bin.exe plus __debug_bin.exe in DAP mode) and removes it on a graceful exit. When shutdown doesn't reach that cleanup the binaries pile up in the project directory. After the session is down — the adapter disconnected and its process reaped — remove the leftover files matching Delve's own names from the session's cwd. Only a locally started adapter (no debugServer) is swept: a remote Delve's cwd may be on another machine. Files that merely share the prefix (__debug_bin.go sources, in-progress names with other suffixes, directories) are left alone. Fixes #1790 --- crates/ide-dap/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ crates/ide-dap/src/tests.rs | 23 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/crates/ide-dap/src/lib.rs b/crates/ide-dap/src/lib.rs index 6971794a2..524c10bea 100644 --- a/crates/ide-dap/src/lib.rs +++ b/crates/ide-dap/src/lib.rs @@ -530,6 +530,34 @@ fn unverified(enabled: impl Iterator) -> Vec { .collect() } +/// Delve compiles the debuggee to `__debug_bin` in the session's cwd (on Windows +/// `__debug_bin.exe` plus `__debug_bin.exe` in DAP mode) and removes it on a +/// graceful exit — a shutdown it doesn't always reach, so the files pile up. Sweep +/// the leftovers once the session is down. Runs after `session_closed`, so Delve +/// has already exited or been killed and holds no file locks; a leftover of a +/// still-building run is removed on that run's own shutdown. +fn remove_delve_binaries(cwd: &Path) { + let Ok(entries) = std::fs::read_dir(cwd) else { return }; + for entry in entries.flatten() { + let name = entry.file_name(); + let Some(name) = name.to_str() else { continue }; + // Windows writes `__debug_bin.exe` (and `.exe` in DAP mode); other + // platforms keep the bare name. Suffixes like `.go` are user files. + let suffix = name.strip_prefix("__debug_bin"); + let leftover = suffix.is_some_and(|s| { + s.is_empty() + || s == ".exe" + || (s.starts_with(".exe") && s[".exe".len()..].chars().all(|c| c.is_ascii_digit())) + }); + if leftover + && entry.file_type().is_ok_and(|t| t.is_file()) + && let Err(e) = std::fs::remove_file(entry.path()) + { + tracing::debug!("could not remove Delve leftover {}: {e}", entry.path().display()); + } + } +} + pub(crate) fn variable_from(v: &Value) -> Variable { let s = |k: &str| v.get(k).and_then(Value::as_str).map(str::to_owned); Variable { @@ -981,6 +1009,10 @@ impl DebugManager { } s.conn.close(); self.session_closed(s); + if s.adapter == "delve" && s.spec.server_host.is_none() { + let cwd = s.spec.cwd.clone(); + tokio::task::spawn_blocking(move || remove_delve_binaries(&cwd)); + } } /// Breakpoints are kept per file even without a session and sent on start. diff --git a/crates/ide-dap/src/tests.rs b/crates/ide-dap/src/tests.rs index d82a7e673..2f728a9f3 100644 --- a/crates/ide-dap/src/tests.rs +++ b/crates/ide-dap/src/tests.rs @@ -1347,3 +1347,26 @@ fn flutter_projects_are_found_from_the_program_or_the_working_directory() { assert!(!is_flutter_project(&root, None)); std::fs::remove_dir_all(&root).unwrap(); } + +#[test] +fn delve_leftovers_are_removed_without_touching_other_files() { + let dir = std::env::temp_dir().join(format!("openidea-delve-bin-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + for name in ["__debug_bin", "__debug_bin.exe", "__debug_bin.exe3323301925"] { + std::fs::write(dir.join(name), b"bin").unwrap(); + } + // Lookalikes that are not Delve's own binaries stay: sources, a future + // in-progress build, and a directory that happens to share the name. + for name in ["__debug_bin.go", "__debug_bin.go.exe", "main.go"] { + std::fs::write(dir.join(name), b"keep").unwrap(); + } + std::fs::create_dir(dir.join("__debug_bin.dir")).unwrap(); + remove_delve_binaries(&dir); + for name in ["__debug_bin", "__debug_bin.exe", "__debug_bin.exe3323301925"] { + assert!(!dir.join(name).exists(), "{name} should be gone"); + } + for name in ["__debug_bin.go", "__debug_bin.go.exe", "main.go", "__debug_bin.dir"] { + assert!(dir.join(name).exists(), "{name} should stay"); + } + std::fs::remove_dir_all(&dir).unwrap(); +}