Skip to content

Commit 2f3daba

Browse files
committed
Defer weakref clears only for refs to classes.
This avoids breaking tests while fixing the issue with tp_subclasses. In the long term, it would be better to defer the clear of all weakrefs, not just the ones referring to classes. However, that is a more distruptive change and would seem to have a higher chance of breaking user code. So, it would not be something to do in a bugfix release.
1 parent 12f0b5c commit 2f3daba

File tree

9 files changed

+64
-47
lines changed

9 files changed

+64
-47
lines changed

Lib/_threading_local.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def local_deleted(_, key=key):
4949
# When the localimpl is deleted, remove the thread attribute.
5050
thread = wrthread()
5151
if thread is not None:
52-
thread.__dict__.pop(key, None)
52+
del thread.__dict__[key]
5353
def thread_deleted(_, idt=idt):
5454
# When the thread is deleted, remove the local dict.
5555
# Note that this is suboptimal if the thread object gets

Lib/test/test_finalization.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,8 @@ def test_simple_resurrect(self):
280280
gc.collect()
281281
self.assert_del_calls(ids)
282282
self.assert_survivors(ids)
283-
# This used to be None because weakrefs were cleared before
284-
# calling finalizers. Now they are cleared after.
285-
self.assertIsNot(wr(), None)
283+
# XXX is this desirable?
284+
self.assertIs(wr(), None)
286285
# When trying to destroy the object a second time, __del__
287286
# isn't called anymore (and the object isn't resurrected).
288287
self.clear_survivors()
@@ -389,10 +388,8 @@ def check_resurrecting_chain(self, classes):
389388
gc.collect()
390389
self.assert_del_calls(ids)
391390
self.assert_survivors(survivor_ids)
392-
for wr in wrs:
393-
# These values used to be None because weakrefs were cleared
394-
# before calling finalizers. Now they are cleared after.
395-
self.assertIsNotNone(wr())
391+
# XXX desirable?
392+
self.assertEqual([wr() for wr in wrs], [None] * N)
396393
self.clear_survivors()
397394
gc.collect()
398395
self.assert_del_calls(ids)

Lib/test/test_gc.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -658,11 +658,9 @@ def callback(ignored):
658658
gc.collect()
659659
self.assertEqual(len(ouch), 2) # else the callbacks didn't run
660660
for x in ouch:
661-
# In previous versions of Python, the weakrefs are cleared
662-
# before calling finalizers. Now they are cleared after.
663-
# So when the object is resurrected, the weakref is not
664-
# cleared since it is no longer unreachable.
665-
self.assertIsInstance(x, C1055820)
661+
# If the callback resurrected one of these guys, the instance
662+
# would be damaged, with an empty __dict__.
663+
self.assertEqual(x, None)
666664

667665
def test_bug21435(self):
668666
# This is a poor test - its only virtue is that it happened to
@@ -1049,8 +1047,8 @@ class Z:
10491047
# release references and create trash
10501048
del a, wr_cycle
10511049
gc.collect()
1052-
# In older versions of Python, the weakref was cleared by the
1053-
# gc. Now it is not cleared and so the callback is run.
1050+
# if called, it means there is a bug in the GC. The weakref should be
1051+
# cleared before Z dies.
10541052
callback.assert_not_called()
10551053
gc.enable()
10561054

@@ -1332,7 +1330,6 @@ def setUp(self):
13321330
def tearDown(self):
13331331
gc.disable()
13341332

1335-
@unittest.skipIf(Py_GIL_DISABLED, "requires GC generations or increments")
13361333
def test_bug1055820c(self):
13371334
# Corresponds to temp2c.py in the bug report. This is pretty
13381335
# elaborate.
@@ -1408,7 +1405,6 @@ def callback(ignored):
14081405
self.assertEqual(x, None)
14091406

14101407
@gc_threshold(1000, 0, 0)
1411-
@unittest.skipIf(Py_GIL_DISABLED, "requires GC generations or increments")
14121408
def test_bug1055820d(self):
14131409
# Corresponds to temp2d.py in the bug report. This is very much like
14141410
# test_bug1055820c, but uses a __del__ method instead of a weakref

Lib/test/test_io.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -807,12 +807,7 @@ def test_closefd_attr(self):
807807
def test_garbage_collection(self):
808808
# FileIO objects are collected, and collecting them flushes
809809
# all data to disk.
810-
#
811-
# Note that using warnings_helper.check_warnings() will keep the
812-
# file alive due to the `source` argument to warn(). So, use
813-
# catch_warnings() instead.
814-
with warnings.catch_warnings():
815-
warnings.simplefilter("ignore", ResourceWarning)
810+
with warnings_helper.check_warnings(('', ResourceWarning)):
816811
f = self.FileIO(os_helper.TESTFN, "wb")
817812
f.write(b"abcxxx")
818813
f.f = f
@@ -1813,11 +1808,7 @@ def test_garbage_collection(self):
18131808
# C BufferedReader objects are collected.
18141809
# The Python version has __del__, so it ends into gc.garbage instead
18151810
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
1816-
# Note that using warnings_helper.check_warnings() will keep the
1817-
# file alive due to the `source` argument to warn(). So, use
1818-
# catch_warnings() instead.
1819-
with warnings.catch_warnings():
1820-
warnings.simplefilter("ignore", ResourceWarning)
1811+
with warnings_helper.check_warnings(('', ResourceWarning)):
18211812
rawio = self.FileIO(os_helper.TESTFN, "w+b")
18221813
f = self.tp(rawio)
18231814
f.f = f
@@ -2166,11 +2157,7 @@ def test_garbage_collection(self):
21662157
# all data to disk.
21672158
# The Python version has __del__, so it ends into gc.garbage instead
21682159
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
2169-
# Note that using warnings_helper.check_warnings() will keep the
2170-
# file alive due to the `source` argument to warn(). So, use
2171-
# catch_warnings() instead.
2172-
with warnings.catch_warnings():
2173-
warnings.simplefilter("ignore", ResourceWarning)
2160+
with warnings_helper.check_warnings(('', ResourceWarning)):
21742161
rawio = self.FileIO(os_helper.TESTFN, "w+b")
21752162
f = self.tp(rawio)
21762163
f.write(b"123xxx")
@@ -4092,8 +4079,7 @@ def test_garbage_collection(self):
40924079
# C TextIOWrapper objects are collected, and collecting them flushes
40934080
# all data to disk.
40944081
# The Python version has __del__, so it ends in gc.garbage instead.
4095-
with warnings.catch_warnings():
4096-
warnings.simplefilter("ignore", ResourceWarning)
4082+
with warnings_helper.check_warnings(('', ResourceWarning)):
40974083
rawio = self.FileIO(os_helper.TESTFN, "wb")
40984084
b = self.BufferedWriter(rawio)
40994085
t = self.TextIOWrapper(b, encoding="ascii")

Lib/test/test_weakref.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ def check_len_cycles(self, dict_type, cons):
13141314
n2 = len(dct)
13151315
# one item may be kept alive inside the iterator
13161316
self.assertIn(n1, (0, 1))
1317-
self.assertIn(n2, (0, 1))
1317+
self.assertEqual(n2, 0)
13181318

13191319
def test_weak_keyed_len_cycles(self):
13201320
self.check_len_cycles(weakref.WeakKeyDictionary, lambda k: (k, 1))

Modules/_datetimemodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ clear_current_module(PyInterpreterState *interp, PyObject *expected)
218218
if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) {
219219
goto error;
220220
}
221-
if (ref != NULL && ref != Py_None) {
221+
if (ref != NULL) {
222222
PyObject *current = NULL;
223223
int rc = PyWeakref_GetRef(ref, &current);
224224
/* We only need "current" for pointer comparison. */

Modules/gc_weakref.txt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
Intro
22
=====
33

4-
**************************************************************************
5-
Note: this document was written long ago, before PEP 442 (safe object
6-
finalization) was implemented. While that has changed some things, this
7-
document is still mostly accurate. Just note that the rules being discussed
8-
here apply to the unreachable set of objects *after* non-legacy finalizers
9-
have been called. Also, the clearing of weakrefs has been changed to happen
10-
later in the collection (after running finalizers but before tp_clear is
11-
called).
12-
**************************************************************************
13-
144
The basic rule for dealing with weakref callbacks (and __del__ methods too,
155
for that matter) during cyclic gc:
166

Python/gc.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,30 @@ handle_weakref_callbacks(PyGC_Head *unreachable, PyGC_Head *old)
901901
for (PyWeakReference *wr = *wrlist; wr != NULL; wr = next_wr) {
902902
next_wr = wr->wr_next;
903903

904+
// Since Python 2.3, weakrefs to cyclic garbage have been cleared
905+
// *before* calling finalizers. However, since tp_subclasses
906+
// started being necessary to invalidate caches (e.g. by
907+
// PyType_Modified()), that clearing has created a bug. If the
908+
// weakref to the subclass is cleared before a finalizer is
909+
// called, the cache may not be correctly invalidated. That can
910+
// lead to segfaults since the caches can refer to deallocated
911+
// objects. Delaying the clear of weakrefs until *after*
912+
// finalizers have been called fixes that bug. However, that can
913+
// introduce other problems since some finalizer code expects that
914+
// the weakrefs will be cleared first. The "multiprocessing"
915+
// package contains finalizer logic like this, for example. So,
916+
// we have the PyType_Check() test above and only defer the clear
917+
// of types. That solves the issue for tp_subclasses. In a
918+
// future version of Python, we should likely defer the weakref
919+
// clear for all objects, not just types.
920+
if (!PyType_Check(wr->wr_object)) {
921+
// _PyWeakref_ClearRef clears the weakref but leaves the
922+
// callback pointer intact. Obscure: it also changes *wrlist.
923+
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == op);
924+
_PyWeakref_ClearRef(wr);
925+
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None);
926+
}
927+
904928
if (wr->wr_callback == NULL) {
905929
/* no callback */
906930
continue;

Python/gc_free_threading.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,30 @@ find_weakref_callbacks(struct collection_state *state)
15131513
for (PyWeakReference *wr = *wrlist; wr != NULL; wr = next_wr) {
15141514
next_wr = wr->wr_next;
15151515

1516+
// Since Python 2.3, weakrefs to cyclic garbage have been cleared
1517+
// *before* calling finalizers. However, since tp_subclasses
1518+
// started being necessary to invalidate caches (e.g. by
1519+
// PyType_Modified()), that clearing has created a bug. If the
1520+
// weakref to the subclass is cleared before a finalizer is
1521+
// called, the cache may not be correctly invalidated. That can
1522+
// lead to segfaults since the caches can refer to deallocated
1523+
// objects. Delaying the clear of weakrefs until *after*
1524+
// finalizers have been called fixes that bug. However, that can
1525+
// introduce other problems since some finalizer code expects that
1526+
// the weakrefs will be cleared first. The "multiprocessing"
1527+
// package contains finalizer logic like this, for example. So,
1528+
// we have the PyType_Check() test above and only defer the clear
1529+
// of types. That solves the issue for tp_subclasses. In a
1530+
// future version of Python, we should likely defer the weakref
1531+
// clear for all objects, not just types.
1532+
if (!PyType_Check(wr->wr_object)) {
1533+
// _PyWeakref_ClearRef clears the weakref but leaves the
1534+
// callback pointer intact. Obscure: it also changes *wrlist.
1535+
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == op);
1536+
_PyWeakref_ClearRef(wr);
1537+
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None);
1538+
}
1539+
15161540
// We do not invoke callbacks for weakrefs that are themselves
15171541
// unreachable. This is partly for historical reasons: weakrefs
15181542
// predate safe object finalization, and a weakref that is itself

0 commit comments

Comments
 (0)