Skip to content

Commit 713ecbf

Browse files
[3.15] gh-159431: Handle immortal objects in _PyObject_XDecRefDelayed() (GH-159433) (#160229)
Skip immortal objects both when queueing and when processing the queue, and remove the now redundant caller-side checks. (cherry picked from commit b70a512) Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
1 parent 87d7436 commit 713ecbf

3 files changed

Lines changed: 28 additions & 10 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
@@ -1378,6 +1378,10 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
13781378
return;
13791379
}
13801380
assert(_PyInterpreterState_GET()->stoptheworld.world_stopped);
1381+
if (_Py_IsImmortal(obj)) {
1382+
// The object may have become immortal after it was queued.
1383+
return;
1384+
}
13811385
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
13821386
if (refcount == 0) {
13831387
cb(obj, state);
@@ -1470,9 +1474,11 @@ free_delayed(uintptr_t ptr, size_t size)
14701474
_PyEval_StopTheWorld(tstate->base.interp);
14711475
if (work_item_should_decref(ptr)) {
14721476
PyObject *obj = (PyObject *)(ptr - 1);
1473-
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
1474-
if (refcount == 0) {
1475-
to_dealloc = obj;
1477+
if (!_Py_IsImmortal(obj)) {
1478+
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
1479+
if (refcount == 0) {
1480+
to_dealloc = obj;
1481+
}
14761482
}
14771483
}
14781484
else {
@@ -1520,7 +1526,7 @@ void
15201526
_PyObject_XDecRefDelayed(PyObject *ptr)
15211527
{
15221528
assert(!((uintptr_t)ptr & 0x01));
1523-
if (ptr != NULL) {
1529+
if (ptr != NULL && !_Py_IsImmortal(ptr)) {
15241530
// We use 0 as the size since we don't have an easy way to know the
15251531
// actual size. If we are freeing many objects, the write sequence
15261532
// will be advanced due to QSBR_DEFERRED_LIMIT.
@@ -1535,12 +1541,7 @@ _PyObject_XSetRefDelayed(PyObject **ptr, PyObject *value)
15351541
{
15361542
PyObject *old = *ptr;
15371543
FT_ATOMIC_STORE_PTR_RELEASE(*ptr, value);
1538-
if (old == NULL) {
1539-
return;
1540-
}
1541-
if (!_Py_IsImmortal(old)) {
1542-
_PyObject_XDecRefDelayed(old);
1543-
}
1544+
_PyObject_XDecRefDelayed(old);
15441545
}
15451546
#endif
15461547

0 commit comments

Comments
 (0)