GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

No menu could be recovered. Follow the blue links in the page.

This view

  • Original page
  • New address

GeezerVison is showing objects.githubusercontent.com — objects.githubusercontent.com

diff --git a/Lib/test/test_free_threading/test_type.py b/Lib/test/test_free_threading/test_type.py index a55c481..a4806ee 100644 --- a/Lib/test/test_free_threading/test_type.py +++ b/Lib/test/test_free_threading/test_type.py @@ -401,6 +401,54 @@ class B(A): with threading_helper.start_threads(threads): pass + def test_freeze_concurrent_setattr_deadlock(self): + # PyType_Freeze() stopped the world while holding the type lock + # without preventing its release. If it blocked on the stop-the-world + # mutex, the type lock was released and a thread assigning a special + # method could take it and then wait for the stop-the-world mutex, + # while the freezing thread was granted that mutex and waited to + # re-acquire the type lock. + _testlimitedcapi = import_helper.import_module("_testlimitedcapi") + type_freeze = _testlimitedcapi.type_freeze + N = 100 + done = False + + def freezer(): + nonlocal done + barrier.wait() + try: + for _ in range(N): + class C: + pass + type_freeze(C) + finally: + done = True + + def setter(): + class K: + pass + def eq(self, other): + return NotImplemented + barrier.wait() + while not done: + K.__eq__ = eq + del K.__eq__ + + def stw_user(): + # Stops the world without holding the type lock, so that the + # freezer has to wait for the stop-the-world mutex. + def f(): + pass + barrier.wait() + while not done: + f.__qualname__ = "f" + + targets = (freezer, setter, stw_user) + barrier = threading.Barrier(len(targets)) + threads = [Thread(target=target) for target in targets] + with threading_helper.start_threads(threads): + pass + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-23-00-00-00.gh-issue-NNNNNN.FrzStw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-23-00-00-00.gh-issue-NNNNNN.FrzStw.rst new file mode 100644 index 0000000..cce9cf9 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-23-00-00-00.gh-issue-NNNNNN.FrzStw.rst @@ -0,0 +1,6 @@ +Fix a deadlock in the :term:`free threaded ` build when +:c:func:`PyType_Freeze` is called while another thread assigns a special +method on a class and a third stops the world (for example, the garbage +collector). This could happen when an extension module that freezes its +types, such as :mod:`!_zstd` (imported by :mod:`shutil`, :mod:`tempfile` and +:mod:`zipfile`), was first imported concurrently with class creation. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b9e1142..ddba027 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -12463,10 +12463,18 @@ PyType_Freeze(PyTypeObject *type) return -1; } + pinned_mutexes_t pinned; BEGIN_TYPE_LOCK(); + // Keep TYPE_LOCK held while waiting for the stop-the-world mutex. If it + // were released, another thread could acquire it (with + // type_lock_prevent_release()) and block on the stop-the-world mutex + // while this thread, having been granted that mutex, waits to re-acquire + // TYPE_LOCK: a lock-order inversion. + type_lock_prevent_release(&pinned); types_stop_world(); type_add_flags(type, Py_TPFLAGS_IMMUTABLETYPE); types_start_world(); + type_lock_allow_release(&pinned); ASSERT_TYPE_LOCK_HELD(); _PyType_Modified_Unlocked(type); END_TYPE_LOCK();
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.