Skip to content

馃悰 fix(ci): dump stacks from a thread that holds the GIL - #3377

Merged
gaborbernat merged 1 commit into
pypa:mainfrom
gaborbernat:test/diagnostics-wait-own-loop
Oct 2, 2026
Merged

gaborbernat merged 1 commit into
pypa:mainfrom
gaborbernat:test/diagnostics-wait-own-loop

Conversation

@gaborbernat

Copy link
Copy Markdown
Contributor

faulthandler.dump_traceback_later reads other threads' frames without the GIL, so a dump that races a thread starting, exiting or shrinking its stack can fault in the watchdog. On macOS the faulting thread loops instead of dying, and the process hangs at exit in cancel_dump_traceback_later; #3362 caught the same fault in _PyFrame_SafeGetCode under LLDB. CPython documents the frame checks as heuristics and has no fix in 3.14.8 or later.

Before After
periodic dumps native watchdog, no GIL daemon thread calling faulthandler.dump_traceback, which holds the GIL (stops the world on free-threaded builds)
thread holding the GIL native watchdog dumps native timer, re-armed after each dump at 10 intervals, dumps once the thread stalls that long
exit cancel_dump_traceback_later could wait forever stop and join the thread, then cancel the timer

The self-tests count the new DUMP records, wait for each process's own pytest_runtestloop frame, and gain a test that holds the GIL with a backtracking regex until the stall timer fires. The diagnostics self-test file passed 200 of 200 runs on 3.14, 75 of 75 on 3.13t and 50 of 50 on 3.14t; before, it failed about once in 50 runs on 3.14.

faulthandler.dump_traceback_later walks other threads' frames without
the GIL. When a frame's memory is freed mid-walk, the watchdog faults;
on macOS it loops instead of dying, so the process hangs at exit while
cancel_dump_traceback_later waits for it. The self-tests dump every
50ms and hit this in about 1 of 50 runs; CI at 60s hits it rarely.

Dump from a daemon thread with faulthandler.dump_traceback, which holds
the GIL on default builds and stops the world on free-threaded ones,
so frames stay valid while it reads them. Keep the native timer as a
stall detector: the thread re-arms it at ten intervals after each
dump, so it fires only when a thread holds the GIL that long. Serialize
the plugin's faulthandler calls, and stop and join the thread at exit.

Claude-Session: https://claude.ai/code/session_018yHsaqnsodWtkwrqQ4kKPr
@gaborbernat
gaborbernat merged commit 9fd48c8 into pypa:main Oct 2, 2026
76 checks passed
@gaborbernat
gaborbernat deleted the test/diagnostics-wait-own-loop branch October 3, 2026 15:12
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