Keep SSH remote job running when its PTY session hangs up - #70573
Conversation
SSHRemoteJobOperator launches the remote job detached under setsid so it survives the SSH connection dropping. The launcher redirected the job's stdout and stderr to /dev/null but left its stdin on the launching terminal. A fresh setsid session leader that still holds a terminal on any file descriptor re-adopts it as its controlling terminal, so when an SSH session that allocated a PTY hangs up, the job received SIGHUP and died -- orphaning the work the operator exists to keep alive. Detaching stdin as well leaves the job in a session with no controlling terminal, immune to the hangup.
|
Thanks for tracking this down, @jason810496. landed on the same RC while chasing this: The detached job keeps the launcher's terminal on fd 0, so the fresh setsid session leader re-acquires it as its controlling terminal. When the pty hangs up, the job gets SIGHUP and dies. Redirecting stdin from /dev/null is what fully detaches it, exactly as you have it on both the setsid and nohup paths. I reproduced it in a Linux container. The controlling-terminal timing makes it intermittent on CI but deterministic in a slower Docker VM, which gives a clean before/after.
Linux 6.10.14-linuxkit aarch64
E AssertionError: job never started test_remote_job.py:426: AssertionError
ps on the job right before the pty hangup. pid == sid == pgid confirms setsid(2) gave it a new session; tty = pts/0 shows it still holds the launcher's pty as its controlling terminal, which is the path the SIGHUP travels down: recorded pid: 31
Reproduce From an apache/airflow checkout. The test skips on macOS, so run it under Linux/Docker: docker run --rm -v "$PWD:/repo" -w /tmp python:3.10 bash -c ' Dropping the @pytest.mark.flaky(reruns=5) marker is the right call too: with stdin detached the test passed 20/20 for me, so it no longer needs reruns. +1 from me. |
|
Yeah, I verified on Linux container as well. Having standard daemonization solves the flaky test issue. Thanks for double checking this! |
SSHRemoteJobOperatorlaunches the remote job detached undersetsidso it survives the SSH connection dropping. The wrapper redirected the job's stdout/stderr to/dev/nullbut left its stdin on the launching terminal. A freshsetsidsession leader that still holds a terminal on any fd re-adopts it as its controlling terminal, so when an SSH session that allocated a PTY (get_pty=True) hangs up, the detached job receivesSIGHUPand dies — orphaning the work the operator exists to keep alive.Redirecting stdin from
/dev/nullas well (standard daemonization) leaves the job in a session with no controlling terminal, immune to the hangup.This is also the root cause of the flaky
test_kill_terminates_whole_job_tree_under_job_control: its PTY harness hangs up the terminal and then asserts the job is still running. The job was being killed by that hangup, which the@pytest.mark.flaky(reruns=5)marker only masked. With the wrapper fixed the test is deterministic, so the marker and its stale "process-group race" comment are removed.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 4.8) following the guidelines