From f70eb13f75347931981f27259692c31fb4979ed7 Mon Sep 17 00:00:00 2001 From: WangTongchao Date: Mon, 9 Nov 2026 09:14:41 +0800 Subject: [PATCH] fix(process): don't spawn taskkill when the child started nothing (#2056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `kill_tree` and `kill_tree_of` run `taskkill /T /F /PID` on Windows even when the child has no descendants, which is the common case for a program a plugin started, read from and closed. `taskkill` is a process spawn — ~0.7 s here, measured on this machine — and it runs inside host calls that are inside a plugin call's budget: dropping eight process handles at once overran a 5 s budget, so the plugin was trapped for something the host did. When `descendants(pid)` is empty the child itself is all there is to kill, and both callers already do that (`kill_tree` follows with `child.kill()` + `child.wait()`, `kill_tree_of` leaves the child to its owner), so the spawn buys nothing. Skip it. Verified: - `cargo test -p ide-core` → 77 passed. - `cargo test -p ide-host --test plugins` → 95 passed, including `a_plugin_keeps_a_process_running_behind_a_handle`, which fails on unmodified `main` on Windows (`plugin "misbehave" timed out after 5s` at its `childdrop` step, which drops eight handles) and now passes in 9.7 s instead of 20 s. - clippy `--all-targets -D warnings` and `cargo fmt --check` clean; no component or WIT change, so nothing to rebuild. Co-authored-by: chao Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> --- crates/ide-core/src/process.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/ide-core/src/process.rs b/crates/ide-core/src/process.rs index 0eb1bae87..e40a9d069 100644 --- a/crates/ide-core/src/process.rs +++ b/crates/ide-core/src/process.rs @@ -110,6 +110,12 @@ const TREE_EXIT_WAIT: Duration = Duration::from_secs(5); #[cfg(windows)] fn taskkill_tree_of(pid: u32) { let started = windows_tree::descendants(pid); + // When the child started nothing, killing it alone is the whole job: + // `taskkill` would be a process spawn (measured at ~0.7 s here) for the + // same outcome, and the callers kill the child themselves. + if started.is_empty() { + return; + } let mut taskkill = Command::new("taskkill"); prepare(&mut taskkill) .args(["/T", "/F", "/PID", &pid.to_string()])