Skip to content

Commit b70a512

Browse files
authored
gh-159431: Handle immortal objects in _PyObject_XDecRefDelayed() (gh-159433)
Skip immortal objects both when queueing and when processing the queue, and remove the now redundant caller-side checks.
1 parent 2588362 commit b70a512

4 files changed

Lines changed: 29 additions & 13 deletions

File tree

‎Lib/test/test_capi/test_object.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,19 @@ def __del__(self):
275275
obj = MyObj()
276276
_testinternalcapi.incref_decref_delayed(obj)
277277

278+
def test_decref_delayed_immortal(self):
279+
# _PyObject_XDecRefDelayed() must handle immortal objects, including
280+
# objects that become immortal after they were queued. The GC
281+
# processes the queue with the world stopped.
282+
import gc
283+
_testinternalcapi.incref_decref_delayed(None)
284+
gc.collect()
285+
286+
s = ''.join(['decref', '_delayed', '_immortal'])
287+
_testinternalcapi.incref_decref_delayed(s)
288+
s = sys.intern(s)
289+
gc.collect()
290+
278291
def test_is_unique_temporary(self):
279292
self.assertTrue(_testcapi.pyobject_is_unique_temporary(object()))
280293
obj = object()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix an assertion failure, or an immortal object becoming mortal, on the
2+
:term:`free threaded <free threading>` build when an object passed to the
3+
internal delayed decref mechanism is immortal or becomes immortal before the
4+
delayed decref is processed by the garbage collector.

‎Objects/obmalloc.c‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,10 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
13841384
return;
13851385
}
13861386
assert(_PyInterpreterState_GET()->stoptheworld.world_stopped);
1387+
if (_Py_IsImmortal(obj)) {
1388+
// The object may have become immortal after it was queued.
1389+
return;
1390+
}
13871391
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
13881392
if (refcount == 0) {
13891393
cb(obj, state);
@@ -1476,9 +1480,11 @@ free_delayed(uintptr_t ptr, size_t size)
14761480
_PyEval_StopTheWorld(tstate->base.interp);
14771481
if (work_item_should_decref(ptr)) {
14781482
PyObject *obj = (PyObject *)(ptr - 1);
1479-
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
1480-
if (refcount == 0) {
1481-
to_dealloc = obj;
1483+
if (!_Py_IsImmortal(obj)) {
1484+
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
1485+
if (refcount == 0) {
1486+
to_dealloc = obj;
1487+
}
14821488
}
14831489
}
14841490
else {
@@ -1526,7 +1532,7 @@ void
15261532
_PyObject_XDecRefDelayed(PyObject *ptr)
15271533
{
15281534
assert(!((uintptr_t)ptr & 0x01));
1529-
if (ptr != NULL) {
1535+
if (ptr != NULL && !_Py_IsImmortal(ptr)) {
15301536
// We use 0 as the size since we don't have an easy way to know the
15311537
// actual size. If we are freeing many objects, the write sequence
15321538
// will be advanced due to QSBR_DEFERRED_LIMIT.
@@ -1541,12 +1547,7 @@ _PyObject_XSetRefDelayed(PyObject **ptr, PyObject *value)
15411547
{
15421548
PyObject *old = *ptr;
15431549
FT_ATOMIC_STORE_PTR_RELEASE(*ptr, value);
1544-
if (old == NULL) {
1545-
return;
1546-
}
1547-
if (!_Py_IsImmortal(old)) {
1548-
_PyObject_XDecRefDelayed(old);
1549-
}
1550+
_PyObject_XDecRefDelayed(old);
15501551
}
15511552
#endif
15521553

‎Objects/typeobject.c‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6738,9 +6738,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
67386738
Py_DECREF(name);
67396739
Py_XDECREF(descr);
67406740
// delay decref of the old value as lock-free type cache readers may access it
6741-
if (old_value != NULL && !_Py_IsImmortal(old_value)) {
6742-
_PyObject_XDecRefDelayed(old_value);
6743-
}
6741+
_PyObject_XDecRefDelayed(old_value);
67446742
return res;
67456743
}
67466744

0 commit comments

Comments
 (0)