fix(process): the supervisor's TERM-to-KILL grace no longer depends on PATH

Review (nit 1): `sleep` is not a dash builtin, so with an unusable PATH (`sleep: not found`) the
supervisor's three one-second waits returned at once and the KILL followed the TERM at +0 s,
cutting short a child's TERM cleanup. The wait now runs `command -p sleep 1` (the shell's default
PATH), falling back to a plain `sleep 1` on a shell without `command -p`.

Test: with PATH=/nonexistent a TERM-ignoring group is still alive 1.5 s after the EOF and is then
KILLed; fails with the plain `sleep`.
This commit is contained in:
kshitijk4poor authored and kshitij committed 2026-10-29 16:58:09 +05:30
1 parent 782c0f847c
commit e883b5a4d5
2 files changed
+26 -1

No files matched your search

+2 -1
View File
@@ -49,7 +49,8 @@ for _ in 1 2 3; do
for g in $reg; do kill -0 -"$g" 2>/dev/null && live="$live $g"; done
reg=$live
[ -n "$reg" ] || exit 0
sleep 1
# The default PATH: a broken PATH must not turn the TERM grace into an instant KILL.
command -p sleep 1 2>/dev/null || sleep 1
done
for g in $reg; do kill -KILL -"$g" 2>/dev/null; done
exit 0
@@ -203,6 +203,30 @@ def test_escalates_to_sigkill_for_a_group_that_ignores_sigterm():
supervisor.wait(timeout=10)
def test_the_term_grace_holds_even_with_a_broken_path():
"""``sleep`` is not a dash builtin: with PATH unusable the grace loop skipped its sleeps and the
KILL landed at once, cutting short a child's TERM cleanup."""
victim = subprocess.Popen(
[sys.executable, "-c",
"import signal, time; signal.signal(signal.SIGTERM, signal.SIG_IGN); print('armed', flush=True); time.sleep(300)"],
stdout=subprocess.PIPE, text=True, start_new_session=True,
)
supervisor = subprocess.Popen(_supervisor_argv(os.getpgid(0)), stdin=subprocess.PIPE, text=True,
start_new_session=True, env={"PATH": "/nonexistent"})
try:
assert victim.stdout.readline().strip() == "armed"
supervisor.stdin.write(f"register {victim.pid}\n")
supervisor.stdin.close()
closed = time.monotonic()
assert _wait_exited(victim), "a SIGTERM-ignoring group survived the reap"
assert time.monotonic() - closed >= 1.5, "the KILL skipped the TERM grace"
finally:
_kill(victim.pid)
_kill(supervisor.pid)
victim.wait(timeout=10)
supervisor.wait(timeout=10)
def test_leaves_an_unregistered_group_alone_at_eof():
# The other failure direction, and the more damaging one: a clean Hermes
# shutdown unregisters as it tears each child down, so EOF must not become