From e2dd5adb01d637fa1c4702de91ad7c10d4e7c784 Mon Sep 17 00:00:00 2001 From: cocolato Date: Sat, 23 May 2026 18:06:36 +0800 Subject: [PATCH 1/4] backup 3.14 --- Lib/test/test_type_cache.py | 20 +++++++++++++++++++ ...-04-15-15-48-04.gh-issue-148450.2MEVqH.rst | 1 + Objects/typeobject.c | 16 +++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 7469a1047f81d7..0390e6496109ec 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -1,4 +1,5 @@ """ Tests for the internal type cache in CPython. """ +import collections.abc import dis import unittest import warnings @@ -114,6 +115,25 @@ class HolderSub(Holder): Holder.set_value() HolderSub.value + def test_abc_register_invalidates_subclass_versions(self): + class Parent: + pass + + class Child(Parent): + pass + + type_assign_version(Parent) + type_assign_version(Child) + parent_version = type_get_version(Parent) + child_version = type_get_version(Child) + if parent_version == 0 or child_version == 0: + self.skipTest("Could not assign valid type versions") + + collections.abc.Mapping.register(Parent) + + self.assertEqual(type_get_version(Parent), 0) + self.assertEqual(type_get_version(Child), 0) + @support.cpython_only class TypeCacheWithSpecializationTests(unittest.TestCase): def tearDown(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst new file mode 100644 index 00000000000000..2a7d0d9bb3a7f7 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst @@ -0,0 +1 @@ +Fix ``abc.register()`` so it invalidates type version tags for registered classes. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0db171807aca4b..560f96009d5c24 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6066,7 +6066,23 @@ void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long flags) { BEGIN_TYPE_LOCK(); + /* Ideally, changing flags and invalidating the old version tag would + happen in one step. But type_modified_unlocked() is re-entrant and + cannot run with the world stopped, so we must invalidate first. + Immutable/static-builtin types are skipped because + set_flags_recursive() does not modify them. */ + if (!PyType_HasFeature(self, Py_TPFLAGS_IMMUTABLETYPE) && + (self->tp_flags & mask) != flags) + { + type_modified_unlocked(self); + } + /* Keep TYPE_LOCK held while waiting for stop-the-world so no thread + can reassign a version tag before the flag update. */ + type_lock_prevent_release(); + types_stop_world(); set_flags_recursive(self, mask, flags); + types_start_world(); + type_lock_allow_release(); END_TYPE_LOCK(); } From 6a360e85f87f885446516eb4f68aef26211de1c0 Mon Sep 17 00:00:00 2001 From: cocolato Date: Sat, 23 May 2026 19:11:47 +0800 Subject: [PATCH 2/4] fix type lock --- Objects/typeobject.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 560f96009d5c24..6df91dd98c5364 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6080,7 +6080,11 @@ _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long can reassign a version tag before the flag update. */ type_lock_prevent_release(); types_stop_world(); + /* Make TYPE_LOCK visible while mutating tp_flags. */ + type_lock_allow_release(); set_flags_recursive(self, mask, flags); + /* Hide TYPE_LOCK again before restarting the world. */ + type_lock_prevent_release(); types_start_world(); type_lock_allow_release(); END_TYPE_LOCK(); From a651c442c392a8d96e2380351020b9df9a506fe7 Mon Sep 17 00:00:00 2001 From: cocolato Date: Sat, 23 May 2026 19:30:04 +0800 Subject: [PATCH 3/4] address review --- Objects/typeobject.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6df91dd98c5364..61e06bf7e657e4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6066,27 +6066,14 @@ void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long flags) { BEGIN_TYPE_LOCK(); - /* Ideally, changing flags and invalidating the old version tag would - happen in one step. But type_modified_unlocked() is re-entrant and - cannot run with the world stopped, so we must invalidate first. - Immutable/static-builtin types are skipped because - set_flags_recursive() does not modify them. */ + /* Invalidate the old version first so + readers cannot assign a fresh tag from stale flags. */ if (!PyType_HasFeature(self, Py_TPFLAGS_IMMUTABLETYPE) && (self->tp_flags & mask) != flags) { type_modified_unlocked(self); + set_flags_recursive(self, mask, flags); } - /* Keep TYPE_LOCK held while waiting for stop-the-world so no thread - can reassign a version tag before the flag update. */ - type_lock_prevent_release(); - types_stop_world(); - /* Make TYPE_LOCK visible while mutating tp_flags. */ - type_lock_allow_release(); - set_flags_recursive(self, mask, flags); - /* Hide TYPE_LOCK again before restarting the world. */ - type_lock_prevent_release(); - types_start_world(); - type_lock_allow_release(); END_TYPE_LOCK(); } From 6f461ed20a1a4bfa431201c460e4e063a5570430 Mon Sep 17 00:00:00 2001 From: cocolato Date: Sat, 23 May 2026 19:34:38 +0800 Subject: [PATCH 4/4] address review --- Objects/typeobject.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 61e06bf7e657e4..0f1660f8f40b21 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6066,14 +6066,16 @@ void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long flags) { BEGIN_TYPE_LOCK(); - /* Invalidate the old version first so - readers cannot assign a fresh tag from stale flags. */ + /* Ideally, changing flags and invalidating the old version tag would happen + in one step. In 3.14, invalidate first while holding TYPE_LOCK so readers + cannot assign a fresh tag from stale flags. Immutable types are skipped by + set_flags_recursive(). */ if (!PyType_HasFeature(self, Py_TPFLAGS_IMMUTABLETYPE) && (self->tp_flags & mask) != flags) { type_modified_unlocked(self); - set_flags_recursive(self, mask, flags); } + set_flags_recursive(self, mask, flags); END_TYPE_LOCK(); }