| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
In the free-threaded build, type slot, MRO and __bases__ updates are prepared under TYPE_LOCK and published with the world stopped. The lock was "pinned" so that it stayed held while the thread waited for the stop-the-world mutex. That deadlocks against a thread that owns the stop-the-world mutex and needs TYPE_LOCK to re-attach, which happens when Python code (a custom mro(), a str subclass __eq__ of a class dict key, or an automatic GC) runs under the type lock and stops the world. Remove the pinning and publish optimistically instead. A type publish generation counter is incremented by every mutation that a prepared update might depend on. The preparing thread snapshots it, prepares, stops the world and compares. If the counter changed, nothing is published and the update is prepared again.
This is more clear than passing the "gen" as a pointer and using NULL to indicate that we don't want to check the generation.
Use explicit stop-the-world pauses and finite worker workloads. Replace slot-update stress with a deterministic reentrancy test, and consolidate queue reference-release coverage.
The rollback on error restores the old bases only if tp_bases is still the tuple this assignment stored, identified by object identity. That breaks if another assignment stores the same tuple object in between. Store a private copy so that the identity check is reliable.
Required since a dropped reference could run type_dealloc(), which takes TYPE_LOCK. Move the final slot_update_clear() after the version tags are invalidated.
Both helpers now return 1 when stored, 0 when the expected value was replaced and -1 when the publish generation changed. The check_gen flag is replaced by the TYPES_PUBLISH_GEN_NONE sentinel.
The checks are plain C and do not run Python code.
|
🤖 New build scheduled with the buildbot fleet by @nascheme for commit c7ac4e7 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F159972%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again. |
Sorry, something went wrong.
|
It looks like this causes an infinite loop: class Other: pass
class Meta(type):
def mro(cls):
Other.__repr__ = lambda self: "x"
return type.mro(cls)
class B: pass
class C(B, metaclass=Meta): pass
C.__bases__ = (B, )
|
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Fixes the free-threaded deadlock described in gh-158195 by removing type-lock pinning, rather than changing how stop-the-world restores critical sections. With this PR, the type lock and class dict lock can now be released normally while a thread waits to stop the world.
Design
Type slot, MRO and __bases__ updates are prepared under TYPE_LOCK and published with the world stopped. Without pinning, another thread can change the state between preparation and publication. Preparation itself can also release the lock or run reentrant Python code, so pinning did not fully protect against stale updates even before this change.
Publication now uses an optimistic retry:
The counter advances on slot-relevant class dict mutations, MRO and bases changes, and slot publication. Ordinary class attribute assignments do not advance it. A single counter keeps validation simple, at the cost of possible retries for unrelated type mutations. Existing type version tags cannot serve this purpose: they may already be invalid (zero) throughout preparation.
A stale update must be recomputed, not simply discarded: an unrelated mutation can invalidate the snapshot without doing the work needed to bring this type's slots or MRO up to date. Reentrancy and rollback checks still avoid overwriting an MRO or bases value that has already been replaced.
Supporting changes
The generation counter and retry checks are free-threaded-only; the GIL build has no generation-tracking overhead.
Backporting
This fix should be backported to 3.14 and 3.15, since the deadlock exists in both. For ABI compatibility, the generation counter could become a process global, rather than inside the interpreter struct.
The change is structured so that the default (GIL) build is essentially unaffected: