Skip to content

Commit 85a67ba

Browse files
authored
gh-161419: fix weakref reviving a dying object in free-threading (#161431)
1 parent d746542 commit 85a67ba

3 files changed

Lines changed: 29 additions & 0 deletions

File tree

‎Include/internal/pycore_object.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,8 @@ _PyObject_ResurrectEnd(PyObject *op)
712712
if (_Py_IsOwnedByCurrentThread(op) && local == 1 && shared == 0) {
713713
// Fast-path: object has a single refcount and is owned by this thread
714714
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0);
715+
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, _Py_UNOWNED_TID);
716+
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED);
715717
# ifdef Py_TRACE_REFS
716718
_Py_ForgetReference(op);
717719
# endif

‎Lib/test/test_weakref.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import gc
2+
import io
23
import sys
34
import doctest
45
import unittest
@@ -1056,6 +1057,29 @@ def __del__(self):
10561057
del head
10571058
self.assertEqual(found, [])
10581059

1060+
def test_ref_to_object_dying_after_finalizer(self):
1061+
cleared = []
1062+
class Attr:
1063+
def __del__(self):
1064+
cleared.append(self.wr() is None)
1065+
1066+
class SetWithDel(set):
1067+
__slots__ = ('a',)
1068+
def __del__(self):
1069+
pass
1070+
1071+
class Raw(io.RawIOBase):
1072+
__slots__ = ('a',)
1073+
1074+
for cls in (SetWithDel, Raw):
1075+
with self.subTest(cls=cls.__name__):
1076+
cleared.clear()
1077+
o = cls()
1078+
o.a = Attr()
1079+
o.a.wr = weakref.ref(o)
1080+
del o
1081+
self.assertEqual(cleared, [True])
1082+
10591083
def test_callback_attribute(self):
10601084
x = Object(1)
10611085
callback = lambda ref: None
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in the :term:`free-threaded build` where a weak reference could
2+
revive an object whose finalizer had already run during deallocation, causing
3+
it to be deallocated twice.

0 commit comments

Comments
 (0)