fix: retry waitpid on EINTR; reap untracked children (stopped-job job-control loss) #20

Merged
ober merged 6 commits from fix/job-control-waitpid-eintr into main 2026-08-25 22:50:53 -04:00
Owner

Problem

A stopped foreground process group could escape job control when a signal such as SIGWINCH interrupted the blocking foreground wait. The shell treated the interrupted wait as a successful exit, so jobs and fg lost the process group even though it remained alive. Unreaped auxiliary children could also accumulate as zombies.

Root cause

ffi_do_waitpid called waitpid once without retrying EINTR. wait-for-foreground-process-raw then mapped the failed wait to an apparent exit.

Changes

  • ffi-shim.c: retry waitpid when it returns EINTR.
  • jobs.ss: reap terminated children with waitpid(-1, WNOHANG), update tracked processes by PID, and continue polling stopped processes.
  • Refresh the branch onto current main and resolve the build/workflow conflicts.
  • Advance VERSION from 0.5.0 to 0.5.1.

Verification

  • make test: 691 jsh unit tests, 25 search UI tests, and 13 secure-history PTY checks passed.
  • make binary: passed on macOS; jsh-macos -c "echo pr20-refresh-smoke" passed.
  • make pre-pr-macos-linux-amd64-all: passed, producing the static stripped jsh-linux-amd64 all-features binary.
  • jobs.ss balance and changed-line security checks passed.

Forgejo CI is re-running against the refreshed branch.

## Problem A stopped foreground process group could escape job control when a signal such as SIGWINCH interrupted the blocking foreground wait. The shell treated the interrupted wait as a successful exit, so `jobs` and `fg` lost the process group even though it remained alive. Unreaped auxiliary children could also accumulate as zombies. ## Root cause `ffi_do_waitpid` called `waitpid` once without retrying `EINTR`. `wait-for-foreground-process-raw` then mapped the failed wait to an apparent exit. ## Changes - `ffi-shim.c`: retry `waitpid` when it returns `EINTR`. - `jobs.ss`: reap terminated children with `waitpid(-1, WNOHANG)`, update tracked processes by PID, and continue polling stopped processes. - Refresh the branch onto current `main` and resolve the build/workflow conflicts. - Advance `VERSION` from 0.5.0 to 0.5.1. ## Verification - `make test`: 691 jsh unit tests, 25 search UI tests, and 13 secure-history PTY checks passed. - `make binary`: passed on macOS; `jsh-macos -c "echo pr20-refresh-smoke"` passed. - `make pre-pr-macos-linux-amd64-all`: passed, producing the static stripped `jsh-linux-amd64` all-features binary. - `jobs.ss` balance and changed-line security checks passed. Forgejo CI is re-running against the refreshed branch.
fix: retry waitpid on EINTR; reap untracked children in job-update-status!
Some checks failed
version-policy / required (pull_request) Successful in 3s
required-ci / required (pull_request) Failing after 8s
07fcaab79e
ffi_do_waitpid returned -1 on EINTR, and wait-for-foreground-process-raw
mapped that to (values 0 #f) = clean exit. A signal delivered to the shell
during the blocking foreground wait (e.g. SIGWINCH from a ,mux pane resize)
therefore made jsh abandon a live child: the process group kept running,
stopped on its next tty access (SIGTTIN), and wedged in T state invisible
to jobs/fg while holding resources (observed with codex's writer lock).

- ffi-shim.c: retry waitpid on EINTR in ffi_do_waitpid.
- jobs.ss: job-update-status! now reaps all terminated children via a
  waitpid(-1, WNOHANG) sweep (kills the zombie accumulation), marks
  tracked processes it reaps by pid, and also polls 'stopped processes
  so a process killed while stopped is still reaped.

Verified on a pty: before, one SIGWINCH during a foreground wait wedged
/bin/sh + reader child in T state with jobs empty and 'fg: %%: no such
job'; after, the wait survives and the job stays managed. Zombie count no
longer grows per command (was +1 per command).
ober scheduled this pull request to auto merge when all checks succeed 2026-08-10 22:56:54 -04:00
fix: use system cc for ffi shims
Some checks failed
version-policy / required (pull_request) Successful in 3s
required-ci / required (pull_request) Failing after 1m33s
6690062813
fix: prepare jsh libraries before tests
Some checks failed
version-policy / required (pull_request) Successful in 3s
required-ci / required (pull_request) Failing after 1m46s
bdf4481318
fix: make jerbuild script readable by runner toolchain
Some checks failed
version-policy / required (pull_request) Successful in 3s
required-ci / required (pull_request) Failing after 1m52s
0e878339c6
fix: avoid list-sort import exclusion
Some checks failed
version-policy / required (pull_request) Successful in 3s
required-ci / required (pull_request) Failing after 2m21s
a11d376a75
Merge main into fix/job-control-waitpid-eintr
All checks were successful
version-policy / required (pull_request) Successful in 4s
required-ci / required (pull_request) Successful in 11m22s
60ee1a1633
ober scheduled this pull request to auto merge when all checks succeed 2026-08-25 22:50:38 -04:00
ober merged commit 740c21d010 into main 2026-08-25 22:50:53 -04:00
ober referenced this pull request from a commit 2026-08-25 22:50:53 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ober/jerboa-shell-extras!20
No description provided.