Skip to content

ci: use pytest-timeout's thread method instead of signal - #587

Merged
azfoo merged 1 commit into
TimeLineAnnotator:devfrom
azfoo:ci-timeout-thread-method
Aug 26, 2026
Merged

ci: use pytest-timeout's thread method instead of signal#587
azfoo merged 1 commit into
TimeLineAnnotator:devfrom
azfoo:ci-timeout-thread-method

Conversation

@azfoo

@azfoo azfoo commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

pytest-timeout defaults to the "signal" method on POSIX, which arms SIGALRM and relies on the interpreter noticing it between bytecode instructions. If the main thread is blocked inside a native call that never returns control to Python -- a genuine OS-level deadlock, not a slow test -- the signal stays pending forever and the handler never runs. --timeout=10 was already configured; it just structurally can't interrupt that class of hang.

The "thread" method runs a separate watcher thread that kills the whole process via os._exit() once the timeout elapses, regardless of what the main thread is doing -- it doesn't need the hung thread's cooperation. Under pytest-xdist (-n auto), that's one worker process; xdist detects the crash, reports the killed test as a failure, and reschedules the rest of that worker's queue onto a replacement worker, so the run doesn't hang.

Trade-off: no clean teardown/reporting for whatever was running at the moment of the kill (os._exit() skips it), and pytest-timeout's own docs note the crash can show up as a generic error rather than a labeled timeout in some report formats. Worth it to turn a silent 30-minute stall into a ~10-second, clearly-attributed failure with a stack trace of every thread at the point of the hang -- which is exactly the diagnostic info we're missing for the mystery hangs currently showing up on #578.

pytest-timeout defaults to the "signal" method on POSIX, which arms SIGALRM and relies on the interpreter noticing it between bytecode instructions. If the main thread is blocked inside a native call that never returns control to Python -- a genuine OS-level deadlock, not a slow test -- the signal stays pending forever and the handler never runs. --timeout=10 was already configured; it just structurally can't interrupt that class of hang, which is exactly what happened on a recent PR's CI run: a job sat blocked for the full 30-minute job timeout instead of failing at 10s.

The "thread" method runs a separate watcher thread that kills the whole process via os._exit() once the timeout elapses, regardless of what the main thread is doing -- it doesn't need the hung thread's cooperation. Under pytest-xdist (-n auto), that's one worker process; xdist detects the crash, reports the killed test as a failure, and reschedules the rest of that worker's queue onto a replacement worker, so the run doesn't hang.

Trade-off: no clean teardown/reporting for whatever was running at the moment of the kill (os._exit() skips it), and pytest-timeout's own docs note the crash can show up as a generic error rather than a labeled timeout in some report formats. Worth it to turn a silent 30-minute stall into a ~10-second, clearly-attributed failure with a stack trace of every thread at the point of the hang.
@azfoo
azfoo merged commit 2b37172 into TimeLineAnnotator:dev Aug 26, 2026
11 checks passed
@azfoo
azfoo deleted the ci-timeout-thread-method branch August 26, 2026 14:00
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