Conversation
tpn
marked this pull request as ready for review
July 3, 2026 23:19
tpn
requested review from
FFY00,
ZeroIntensity and
ericsnowcurrently
as code owners
July 3, 2026 23:19
There was a problem hiding this comment.
Pull request overview
Fixes a free-threaded stop-the-world (STW) fairness issue where repeated STW requests could repeatedly re-park a thread that is trying to reattach, leading to starvation (notably after returning from detached operations like time.sleep()).
Changes:
- Add a distinct thread-state for threads suspended while detached (
_Py_THREAD_SUSPENDED_DETACHED) and update STW parking/unparking logic accordingly. - Track “attach-waiters” across STW passes via a new cold
_PyThreadStateImplflag (stw_attach_waiting) reused from existing padding, so subsequent STW requests avoid re-parking those threads until they attach. - Add a regression test exercising a tight
gc.collect()loop vs. a sleeping thread, plus a NEWS entry and updated state documentation.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Python/pystate.c | Implements the new suspended-detached state and attach-waiter logic to prevent STW starvation. |
| Include/internal/pycore_pystate.h | Documents the new suspended-detached state and updates state constants/diagram. |
| Include/internal/pycore_tstate.h | Adds stw_attach_waiting in reused tail padding for free-threaded builds. |
| Lib/test/test_free_threading/test_gc.py | Adds a subprocess regression test for STW starvation during tight GC loops. |
| Misc/NEWS.d/next/Core_and_Builtins/2026-06-16-19-20-00.gh-issue-151518.e6v0Js.rst | Announces the fix in NEWS. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
4 tasks
Free-threaded stop-the-world pauses can otherwise starve a thread trying to reattach after it was suspended while detached. A tight manual gc.collect() loop can release and immediately request the next stop-the-world pause, repeatedly parking the detached thread before it can attach and make progress. Add a distinct _Py_THREAD_SUSPENDED_DETACHED state for tstates parked from DETACHED. tstate_wait_attach() marks an attach waiter only after observing that detached-origin suspended state, and park_detached_threads() skips only those active waiters on later stop-the-world passes. The ordinary successful tstate_try_attach() path remains the baseline CAS-only path. Teach the related stop-the-world paths about both suspended states, including start_the_world() and tstate_delete_common(). Keep the new wait flag after the existing hot free-threaded _PyThreadStateImpl fields so their offsets do not move. Add a free-threaded GC regression test that runs a subprocess with a tight gc.collect() worker and verifies the main thread can reattach after sleeping and stop the worker.
tpn
force-pushed
the
gh-151518-stw-attach-fairness
branch
from
September 20, 2026 04:01
b1889c7 to
b3b504c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #151518.
Repeated stop-the-world requests can starve a thread returning from a detached operation. After
start_the_world()restores its state toDETACHED, the next requester can suspend it again before its OS thread completes_PyThreadState_Attach().This change distinguishes thread states suspended while detached from those suspended while attached. An active attach waiter records that it encountered the detached-origin suspension; subsequent stop-the-world scans let that waiter attach before suspending it again. The waiter remains in the stop-the-world countdown until it actually stops. Threads detached for sleep or I/O without an active attach attempt remain immediately parkable, and the uncontended attach path remains a single CAS.
The waiting flag reuses trailing
_PyThreadStateImplpadding. Existing state numbers are preserved, and both suspended states resume directly toDETACHED.The subprocess regression explicitly uses
-X gil=0, waits for the collector to start, and repeatedly returns from sleep while another thread callsgc.collect()in a tight loop. It performs 50 reattachments with one second of total intentional sleep and relies on the test framework's outer timeout for stalled progress or shutdown.Validation on Linux x86-64 after rebasing onto
89c67a98aee:--with-pydebug --enable-safety --enable-slower-safety --disable-gil):test_free_threading test_gc test_threading test_capi test_embedpassed, 2,211 tests run.test_interpretersis unsupported in this build and skipped.test_free_threading test_gc test_threading test_embedpassed, 647 tests run.-X gil=1: GC, threading, embedding, and free-threaded GC tests passed, 403 tests run. The final regression also passed separately with this parent setting.test_gc test_threading test_capi test_embed test_interpreterspassed, 2,132 tests run.test_free_threadingskipped as expected.-R 3:3reference-leak checking. Seven additional stress trials passed with mixed global/local pauses, repeated detach/reattach, and workers blocked waiting for collector completion._PyThreadStateImplremains 18,200 bytes, and the new flag overlays padding at offset 18,136.make patchcheckandgit diff --checkpassed.The original B200 experiment reported a timeout in 2/3 unpatched runs and 5/5 patched passes. On this host, current unpatched
mainpassed all three trials of the updated regression (2.46–4.63 seconds), so these local results do not re-establish a failing baseline. Reproduction remains sensitive to scheduling and hardware.These local builds lacked the optional
_decimal,_hashlib,_ssl, and_tkintermodules;_sqlite3was disabled.