Skip to content

Terminal: Close process groups on shutdown, only when intended - #62595

Open
feitreim wants to merge 4 commits into
zed-industries:mainfrom
feitreim:terminal-close-process-groups-v2
Open

Terminal: Close process groups on shutdown, only when intended#62595
feitreim wants to merge 4 commits into
zed-industries:mainfrom
feitreim:terminal-close-process-groups-v2

Conversation

@feitreim

@feitreim feitreim commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Objective

Re-fixes: #47412
Fixes #62286
Fixes #62095
Fixes the bug introduced in #61467

Solution

#47412 was an issue where some processes were not being dropped after zed closed, I fixed that issue in #61467, but then introduced the issues in #62286 and #62095, tldr: I added something to send additional SIGKILL/SIGTERM signals after the terminal was closed, but when running tasks, these signals were firing on the wrong processes, due to race conditions regarding FD reuse.

The big design change this time around is moving ProcessIdGetter to having an OwnedFD, this categorically prevents the FD race that was observed in previous iterations. Checks for which process to kill are run through the OwnedFD, so there cannot be a race where the raw FD we are holding has been reassigned to a new process group. Where the original process group is created, the session id is set, when we go to tear down the process group, we ensure that the current process group has the correct, matching session id, this prevents the admittedly much less likely pid reuse race.

Testing

Manually tested the process exiting changes, and manually tested the task spawning (many times in a row this time, some overlapped).

Additionally this adds a test for #47412, as well as a simple test for #62286/#62095, and a test for those that creates tasks 10x in a row. There is also a new test for the PID reuse case, this one is deterministic.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • terminal: fixed an issue where long running processes in the are not dropped on Zed closing.
  • tasks: fixed an issue where tasks would kill the process of the next spawned task after being dropped.

…n tasks

Re-lands zed-industries#61467 (reverted in zed-industries#62399) with the races that caused zed-industries#62095
and zed-industries#62286 designed out.

Why the original landing regressed rerun tasks: ProcessIdGetter kept a
raw fd number for the PTY master, but the event loop owns that fd and
closes it as soon as the child exits - long before the completed task's
Terminal entity is dropped. Rerunning a task spawns the replacement
terminal first, whose PTY recycles the freed fd number; when the old
entity was then dropped, tcgetpgrp on the recycled number read the new
terminal's foreground process group and SIGTERM/SIGKILLed it.

Why the tcgetsid guard (zed-industries#62322) was not enough: it validated a
descriptor Zed does not own with non-atomic syscalls (the fd can be
closed or reused between the check and the use), and when the guard
rejected, cleanup still fell back to killpg on the child pid captured
arbitrarily long ago, signalling a possibly recycled id unconditionally.

The re-land replaces both heuristics with two deterministic mechanisms:

* ProcessIdGetter now owns a dup of the PTY master (Arc<OwnedFd>), so
  the descriptor always refers to this terminal's PTY and fd-number
  recycling cannot occur by construction. Once the session dies,
  tcgetpgrp on our own master returns 0, so a completed terminal yields
  no foreground candidate at all.
* Process-group ids are validated against the terminal's session before
  any killpg: the spawned child called setsid, so its pid doubles as the
  session id, and a group is only signalled while getsid(pgid) still
  reports that session. A completed terminal therefore captures nothing
  (its drop is signal-free), and a recycled pid lives in a different
  session and is rejected. Validation happens once at capture time so
  the SIGKILL escalation still reaches groups whose leader the SIGTERM
  already killed. kill_child_process and kill_current_process's
  stale-fallback path (kill task) get the same session-leader guard.

Fixes zed-industries#47412
… test

Each drop of a completed terminal frees its PTY fd number for immediate
reuse by the next spawn, so a single spawn/drop cycle can miss a
stale-fd or stale-pid race by timing luck. Loop the rerun cycle from
zed-industries#62095 ten times within one test, asserting on every iteration that the
replacement's task runs to completion untouched.
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 13, 2026
@zed-community-bot zed-community-bot Bot added the guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions label Aug 13, 2026
@ChristopherBiscardi ChristopherBiscardi self-assigned this Aug 13, 2026
@dinocosta dinocosta added the area:integrations/terminal Feedback for terminal integration, shell commands, etc label Aug 14, 2026
@feitreim feitreim changed the title Terminal close process groups v2 Terminal: Close process groups on shutdown without introducing other bugs Aug 14, 2026
@feitreim feitreim changed the title Terminal: Close process groups on shutdown without introducing other bugs Terminal: Close process groups on shutdown, only when intended Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:integrations/terminal Feedback for terminal integration, shell commands, etc cla-signed The user has signed the Contributor License Agreement guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Task won't re-run Tasks terminate with SIGKILL when re-run in an existing terminal Child processes not terminated when integrated terminal closes

3 participants