Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,19 @@ def __del__(self):
obj = MyObj()
_testinternalcapi.incref_decref_delayed(obj)

def test_decref_delayed_immortal(self):
# _PyObject_XDecRefDelayed() must handle immortal objects, including
# objects that become immortal after they were queued. The GC
# processes the queue with the world stopped.
import gc
_testinternalcapi.incref_decref_delayed(None)
gc.collect()

s = ''.join(['decref', '_delayed', '_immortal'])
_testinternalcapi.incref_decref_delayed(s)
s = sys.intern(s)
gc.collect()

def test_is_unique_temporary(self):
self.assertTrue(_testcapi.pyobject_is_unique_temporary(object()))
obj = object()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix an assertion failure, or an immortal object becoming mortal, on the
:term:`free threaded <free threading>` build when an object passed to the
internal delayed decref mechanism is immortal or becomes immortal before the
delayed decref is processed by the garbage collector.
21 changes: 11 additions & 10 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,10 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
return;
}
assert(_PyInterpreterState_GET()->stoptheworld.world_stopped);
if (_Py_IsImmortal(obj)) {
// The object may have become immortal after it was queued.
return;
}
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
if (refcount == 0) {
cb(obj, state);
Expand Down Expand Up @@ -1476,9 +1480,11 @@ free_delayed(uintptr_t ptr, size_t size)
_PyEval_StopTheWorld(tstate->base.interp);
if (work_item_should_decref(ptr)) {
PyObject *obj = (PyObject *)(ptr - 1);
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
if (refcount == 0) {
to_dealloc = obj;
if (!_Py_IsImmortal(obj)) {
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(obj, -1);
if (refcount == 0) {
to_dealloc = obj;
}
}
}
else {
Expand Down Expand Up @@ -1526,7 +1532,7 @@ void
_PyObject_XDecRefDelayed(PyObject *ptr)
{
assert(!((uintptr_t)ptr & 0x01));
if (ptr != NULL) {
if (ptr != NULL && !_Py_IsImmortal(ptr)) {
// We use 0 as the size since we don't have an easy way to know the
// actual size. If we are freeing many objects, the write sequence
// will be advanced due to QSBR_DEFERRED_LIMIT.
Expand All @@ -1541,12 +1547,7 @@ _PyObject_XSetRefDelayed(PyObject **ptr, PyObject *value)
{
PyObject *old = *ptr;
FT_ATOMIC_STORE_PTR_RELEASE(*ptr, value);
if (old == NULL) {
return;
}
if (!_Py_IsImmortal(old)) {
_PyObject_XDecRefDelayed(old);
}
_PyObject_XDecRefDelayed(old);
}
#endif

Expand Down
4 changes: 1 addition & 3 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6738,9 +6738,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
Py_DECREF(name);
Py_XDECREF(descr);
// delay decref of the old value as lock-free type cache readers may access it
if (old_value != NULL && !_Py_IsImmortal(old_value)) {
_PyObject_XDecRefDelayed(old_value);
}
_PyObject_XDecRefDelayed(old_value);
return res;
}

Expand Down
Loading