Files
zed/crates/util/Cargo.toml
T
316ab20bb5 util: Use job objects to reap spawned process trees on Windows (#58885)
On Windows, `util::process::Child::kill()` only terminated the direct
child process, and nothing tied the lifetime of spawned process trees to
Zed. External agent servers launched through ACP (e.g. `claude-code-acp`
via `npx`) spawn node workers and MCP servers as grandchildren, which
were orphaned on every session teardown and accumulated indefinitely —
hundreds of idle `node.exe` processes and GBs of RAM over days.

This PR assigns spawned processes to a Win32 job object configured with
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`:

- `kill()` now calls `TerminateJobObject`, killing the entire tree
instead of just the (shell wrapper) child.
- Dropping `Child` closes the job handle, which makes the OS reap the
tree — including when Zed exits for any reason (even crashes), since the
OS closes its handles.
- Unix behavior (process groups via `killpg`) is unchanged.

Added two Windows tests that spawn a real `powershell -> ping` process
tree and assert the grandchild is terminated on `kill()` and on drop.
Both fail without the fix and pass with it. Verified with `cargo test -p
util`, `script/clippy -p util`, and `cargo check -p agent_servers -p
dap` on Windows 11.

Closes #58873

Release Notes:

- Fixed external agent servers and debug adapters leaking helper
processes (e.g. node workers and MCP servers) on Windows.

---------

Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2026-06-15 10:55:39 +00:00

69 lines
1.5 KiB
TOML

[package]
name = "util"
version = "0.1.0"
edition.workspace = true
publish = false
license = "Apache-2.0"
description = "A collection of utility structs and functions used by Zed and GPUI"
[lints]
workspace = true
[lib]
path = "src/util.rs"
doctest = true
[features]
test-support = ["rand", "util_macros"]
[dependencies]
anyhow.workspace = true
async_zip.workspace = true
collections.workspace = true
dunce.workspace = true
futures-lite.workspace = true
futures.workspace = true
globset.workspace = true
itertools.workspace = true
log.workspace = true
rand = { workspace = true, optional = true }
regex.workspace = true
rust-embed.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
shlex.workspace = true
take-until.workspace = true
tempfile.workspace = true
unicase.workspace = true
url.workspace = true
percent-encoding.workspace = true
util_macros = { workspace = true, optional = true }
gpui_util.workspace = true
[target.'cfg(not(target_family = "wasm"))'.dependencies]
smol.workspace = true
which.workspace = true
async-fs.workspace = true
walkdir.workspace = true
dirs.workspace = true
[target.'cfg(unix)'.dependencies]
command-fds = "0.3.1"
libc.workspace = true
nix = { workspace = true, features = ["user"] }
[target.'cfg(target_os = "macos")'.dependencies]
mach2.workspace = true
[target.'cfg(windows)'.dependencies]
tendril = "0.4.3"
windows.workspace = true
[dev-dependencies]
rand.workspace = true
util_macros.workspace = true
pretty_assertions.workspace = true