https://github.com/python/cpython/commit/da0ec8c712f68db10a41bb571d752eff60687b3d
commit: da0ec8c712f68db10a41bb571d752eff60687b3d
branch: 3.14
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-10-22T09:31:58Z
summary:

[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 b70a5128c4ef1572e6aa24f9f3739d17eacfafef)

Co-authored-by: Neil Schemenauer <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-18-45-35.gh-issue-159431.mVTMsA.rst
M Lib/test/test_capi/test_object.py
M Objects/obmalloc.c

diff --git a/Lib/test/test_capi/test_object.py 
b/Lib/test/test_capi/test_object.py
index e2345635c47325..fa2bf4fcb521e0 100644
--- a/Lib/test/test_capi/test_object.py
+++ b/Lib/test/test_capi/test_object.py
@@ -241,6 +241,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()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-18-45-35.gh-issue-159431.mVTMsA.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-18-45-35.gh-issue-159431.mVTMsA.rst
new file mode 100644
index 00000000000000..7ed642e4c70791
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-14-18-45-35.gh-issue-159431.mVTMsA.rst
@@ -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.
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index 9af59f13dc82d0..9e7506c415c466 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -1181,6 +1181,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);
@@ -1273,9 +1277,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 {
@@ -1323,7 +1329,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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to