Skip to content

Make SSH remote-job kill test diagnosable and restore its reruns - #70562

Merged
potiuk merged 1 commit into
apache:mainfrom
potiuk:fix-ssh-remote-job-pty-race
Jul 27, 2026
Merged

Make SSH remote-job kill test diagnosable and restore its reruns#70562
potiuk merged 1 commit into
apache:mainfrom
potiuk:fix-ssh-remote-job-pty-race

Conversation

@potiuk

@potiuk potiuk commented Jul 27, 2026

Copy link
Copy Markdown
Member

test_kill_terminates_whole_job_tree_under_job_control failed on main in
this run
with job never wrote its pid file.

Not a root-cause fix — I could not reproduce it. This makes the next occurrence
identify itself and restores the reruns meanwhile.

The failure took 5.16s, i.e. the marker arrived and the whole 5s went on polling for
the pid file. Two very different causes produce exactly that and the test can't tell
them apart: _run_bash_mc_under_pty returns silently on EOF when the marker never
arrives, so a launcher that died instantly looks identical to a job that died after
launch. So:

  • the marker read is now asserted, quoting what the pty produced
  • the empty-pid assertion reports the job dir contents — the wrapper creates it before
    backgrounding anything, so missing means the launcher never got there, empty means
    the job was launched and died

Also closes a real ordering hazard: SUBMIT_DONE only means the launcher returned,
before setsid has forked and exec'd into the job. Closing the pty master hangs up
the terminal, and pty.fork() makes the launcher the session leader, so hanging up in
that window could take the job with it. The pty is now held open until the job records
its own pid. This is hardening — see below.

What I tried: macOS has no setsid(1) so the class skips there. Under Linux in
Docker I ran the real wrapper through this harness 65 times — 25 idle, 40 at 0.35 CPU
against six busy loops — and it recorded its pid every time, with and without the hold.
The runner also had 88G free, so it isn't set -euo pipefail on a full disk.

Reruns: @pytest.mark.flaky(reruns=5) returns. #69384 added it to the sibling test
for an environment-dependent process-group race; #69490 dropped it while writing this
variant. The first attempt's assertion text still reaches the CI log.


Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code (Claude Opus 5) following the guidelines

🤖 Generated with Claude Code

test_kill_terminates_whole_job_tree_under_job_control failed on main with "job
never wrote its pid file". The run took 5.16s, so SUBMIT_DONE arrived promptly and
the whole budget went on polling for a pid file that never appeared.

That symptom has two very different causes and the test cannot tell them apart:
_run_bash_mc_under_pty returns silently when the marker never arrives, so a
launcher that died immediately (EOF, no marker, returns at once) and a job that
died after being launched both surface later as the same empty pid file - and both
produce the same ~5s runtime. Two changes separate them:

- assert the marker was actually seen, quoting what the pty did produce
- report the job directory contents when the pid file stays empty; the wrapper
  creates that directory and the log file before it backgrounds anything, so a
  missing directory means the launcher never got there and an empty one means the
  job was launched and died before its first statement

The teardown also has a real ordering hazard, closed here: the marker only says the
launcher returned, which it does the moment it backgrounds setsid - before that
child has forked, called setsid(2) and exec'd into the job. Closing the pty master
hangs up the terminal, and pty.fork() makes the launcher the session leader, so
hanging up inside that window could take the job down with the session. The pty is
now held open until the job proves it left the session by recording its own pid.

I could not reproduce the failure. macOS has no setsid(1) so the class skips there;
under Linux in Docker the real wrapper ran through this exact harness 65 times, 25
idle and 40 with the container throttled to 0.35 CPU against six busy loops, and
recorded its pid every time with and without the hold. So the hold is hardening, not
a demonstrated fix. Reruns come back for that reason - apache#69384 had them on the
sibling test until apache#69490 dropped the marker while writing this one - and the first
attempt's assertion text still reaches the CI log, so the next occurrence should
identify which half broke.

Generated-by: Claude Opus 5 (1M context)
@potiuk
potiuk merged commit e807d5e into apache:main Jul 27, 2026
65 of 72 checks passed
@potiuk
potiuk deleted the fix-ssh-remote-job-pty-race branch July 27, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants