From c838d521cb8152dad3365bffd96dee80ea7cfa1d Mon Sep 17 00:00:00 2001 From: Neko Asakura Date: Fri, 6 Nov 2026 14:34:13 +0800 Subject: [PATCH] [3.14] gh-161419: fix weakref reviving a dying object in free-threading (GH-161431) (cherry picked from commit 85a67bafb4e0dfc9eeef5f75d6c961e4f6231ace) Co-authored-by: Neko Asakura --- Include/internal/pycore_object.h | 2 ++ Lib/test/test_weakref.py | 24 +++++++++++++++++++ ...-11-05-10-19-48.gh-issue-161419.wc4DV0.rst | 3 +++ 3 files changed, 29 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-19-48.gh-issue-161419.wc4DV0.rst diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 64fb419975670d7..bf49ec0d175b4bf 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -742,6 +742,8 @@ _PyObject_ResurrectEnd(PyObject *op) if (_Py_IsOwnedByCurrentThread(op) && local == 1 && shared == 0) { // Fast-path: object has a single refcount and is owned by this thread _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0); + _Py_atomic_store_uintptr_relaxed(&op->ob_tid, _Py_UNOWNED_TID); + _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED); # ifdef Py_TRACE_REFS _Py_ForgetReference(op); # endif diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 80d8ae0beaccd0f..de4ad2c4d6ef2f7 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -1,4 +1,5 @@ import gc +import io import sys import doctest import unittest @@ -1012,6 +1013,29 @@ def cb(wparent): del root gc.collect() + def test_ref_to_object_dying_after_finalizer(self): + cleared = [] + class Attr: + def __del__(self): + cleared.append(self.wr() is None) + + class SetWithDel(set): + __slots__ = ('a',) + def __del__(self): + pass + + class Raw(io.RawIOBase): + __slots__ = ('a',) + + for cls in (SetWithDel, Raw): + with self.subTest(cls=cls.__name__): + cleared.clear() + o = cls() + o.a = Attr() + o.a.wr = weakref.ref(o) + del o + self.assertEqual(cleared, [True]) + def test_callback_attribute(self): x = Object(1) callback = lambda ref: None diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-19-48.gh-issue-161419.wc4DV0.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-19-48.gh-issue-161419.wc4DV0.rst new file mode 100644 index 000000000000000..0d6605a9eeb5503 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-10-19-48.gh-issue-161419.wc4DV0.rst @@ -0,0 +1,3 @@ +Fix a crash in the :term:`free-threaded build` where a weak reference could +revive an object whose finalizer had already run during deallocation, causing +it to be deallocated twice.