Skip to content

Commit da0ec8c

Browse files
[3.14] gh-159431: Handle immortal objects in _PyObject_XDecRefDelayed() (GH-159433) (#160228)
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 04f97e3 commit da0ec8c

3 files changed

Lines changed: 27 additions & 4 deletions

File tree

‎Lib/test/test_capi/test_object.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,19 @@ def __del__(self):
241241
obj = MyObj()
242242
_testinternalcapi.incref_decref_delayed(obj)
243243

244+
def test_decref_delayed_immortal(self):
245+
# _PyObject_XDecRefDelayed() must handle immortal objects, including
246+
# objects that become immortal after they were queued. The GC
247+
# processes the queue with the world stopped.
248+
import gc
249+
_testinternalcapi.incref_decref_delayed(None)
250+
gc.collect()
251+
252+
s = ''.join(['decref', '_delayed', '_immortal'])
253+
_testinternalcapi.incref_decref_delayed(s)
254+
s = sys.intern(s)
255+
gc.collect()
256+
244257
def test_is_unique_temporary(self):
245258
self.assertTrue(_testcapi.pyobject_is_unique_temporary(object()))
246259
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: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,10 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
11811181
return;
11821182
}
11831183
assert(_PyInterpreterState_GET()->stoptheworld.world_stopped);
1184+
if (_Py_IsImmortal(obj)) {
1185+
// The object may have become immortal after it was queued.
1186+
return;
1187+
}
11841188
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
11851189
if (refcount == 0) {
11861190
cb(obj, state);
@@ -1273,9 +1277,11 @@ free_delayed(uintptr_t ptr, size_t size)
12731277
_PyEval_StopTheWorld(tstate->base.interp);
12741278
if (work_item_should_decref(ptr)) {
12751279
PyObject *obj = (PyObject *)(ptr - 1);
1276-
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
1277-
if (refcount == 0) {
1278-
to_dealloc = obj;
1280+
if (!_Py_IsImmortal(obj)) {
1281+
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
1282+
if (refcount == 0) {
1283+
to_dealloc = obj;
1284+
}
12791285
}
12801286
}
12811287
else {
@@ -1323,7 +1329,7 @@ void
13231329
_PyObject_XDecRefDelayed(PyObject *ptr)
13241330
{
13251331
assert(!((uintptr_t)ptr & 0x01));
1326-
if (ptr != NULL) {
1332+
if (ptr != NULL && !_Py_IsImmortal(ptr)) {
13271333
// We use 0 as the size since we don't have an easy way to know the
13281334
// actual size. If we are freeing many objects, the write sequence
13291335
// will be advanced due to QSBR_DEFERRED_LIMIT.

0 commit comments

Comments
 (0)