Skip to content

ci: make the intermittent connect hang diagnosable and self-healing - #71

Merged
shcheklein merged 1 commit into
mainfrom
ci-connect-hang
Aug 4, 2026
Merged

ci: make the intermittent connect hang diagnosable and self-healing#71
shcheklein merged 1 commit into
mainfrom
ci-connect-hang

Conversation

@shcheklein

Copy link
Copy Markdown
Collaborator

Problem

The test suite intermittently freezes at its first SSH connection against mock-ssh-server — always exactly one ubuntu job per run, random Python version, never macOS. Scheduled runs show it across months (Apr 26, Jul 25, Jul 27–28, and Aug 1–4 were four red nightlies in a row). Because the Test step has timeout-minutes: 1, the job is killed before anything can report: every failure is a mute freeze right after test_sftp_pools.py, with no traceback.

The suite opens essentially one real SSH connection (fsspec's instance cache reuses the filesystem across tests), so the hang always lands on the session's first test_sshfs.py test. asyncssh's login_timeout (120s) would raise a diagnosable error on a stalled handshake, but the 60s step axe always wins.

Change

  • faulthandler_timeout = 60 in pytest config: a test stalling for a minute dumps all thread stacks to the log, so the next occurrence tells us exactly where it is stuck (asyncssh handshake vs mockssh/paramiko accept thread).
  • timeout-minutes: 1 → 3 on the Test step: leaves room for the dump and for login_timeout to fire.
  • --reruns 1 --reruns-delay 2 (pytest-rerunfailures): a transient hang no longer reddens the whole run — the stack dump stays in the log while the retried test goes green.

This intentionally does not claim to fix the underlying race (mock-ssh-server is unmaintained); it converts an opaque recurring red into either a self-healed run with evidence in the log, or a real traceback.

🤖 Generated with Claude Code

The suite intermittently freezes at its first SSH connection against
mock-ssh-server (always a single ubuntu job, random Python version;
observed across months of scheduled runs, e.g. Apr 26, Jul 25-28 and
Aug 1-4 which were four red nightlies in a row). The 1-minute step
timeout killed the job before anything could report, so every failure
was an undiagnosable freeze.

- pytest faulthandler_timeout=60 dumps all thread stacks when a test
  stalls, so the next hang shows where it is stuck
- step timeout of 3 minutes leaves room for the dump and for asyncssh's
  login_timeout (120s) to raise a real error
- pytest-rerunfailures retries the failed test once so a transient hang
  no longer reddens the whole run, while the dump stays in the log

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shcheklein
shcheklein merged commit 1dcfc1a into main Aug 4, 2026
14 checks passed
shcheklein added a commit that referenced this pull request Aug 5, 2026
Handler.run() creates the per-channel command queue with a
check-then-assign while paramiko's transport thread creates it with
setdefault() and puts the command into it from
check_channel_exec_request(). When the exec request lands inside the
window, run() replaces the queue that already holds the command,
handle_client() blocks on Queue.get() forever and the client never
receives an exit status.

This is the intermittent CI hang: every _execute()-based operation
(cp_file, checksum, the move fallback) rolls these dice, and
test_concurrency_for_raw_commands rolls them 16 at a time. Confirmed
by the faulthandler dump from the first run with #71 merged
(handle_client threads parked on Queue.get with the client waiting),
and reproduced deterministically by widening the window with a 5ms
sleep: upstream logic deadlocks on the first round, the same logic
with an atomic setdefault() survives, with or without the delay.
mock-ssh-server is unmaintained, so the method is patched in conftest
instead of upstream.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant