https://github.com/python/cpython/commit/347d3594d3993282c3ae5a6872f36a71fbfbc734
commit: 347d3594d3993282c3ae5a6872f36a71fbfbc734
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-02-11T20:59:31+05:30
summary:

gh-143300: implement `PyUnstable_SetImmortal` for marking objects as immortal 
(#144543)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-02-11-13-30-11.gh-issue-143300.yjB63-.rst
M Doc/c-api/object.rst
M Include/cpython/object.h
M Modules/_testcapi/object.c
M Objects/object.c

diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst
index 992a4383f97241..f71bfebdb2a19a 100644
--- a/Doc/c-api/object.rst
+++ b/Doc/c-api/object.rst
@@ -801,3 +801,20 @@ Object Protocol
    cannot fail.
 
    .. versionadded:: 3.14
+
+.. c:function:: int PyUnstable_SetImmortal(PyObject *op)
+
+   Marks the object *op* :term:`immortal`. The argument should be uniquely 
referenced by
+   the calling thread. This is intended to be used for reducing reference 
counting contention
+   in the :term:`free-threaded build` for objects which are shared across 
threads.
+
+   This is a one-way process: objects can only be made immortal; they cannot be
+   made mortal once again. Immortal objects do not participate in reference 
counting
+   and will never be garbage collected. If the object is GC-tracked, it is 
untracked.
+
+   This function is intended to be used soon after *op* is created, by the 
code that
+   creates it, such as in the object's :c:member:`~PyTypeObject.tp_new` slot.
+   Returns 1 if the object was made immortal and returns 0 if it was not.
+   This function cannot fail.
+
+   .. versionadded:: next
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 28c909531dba64..61cdb93d1d5354 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -493,3 +493,5 @@ PyAPI_FUNC(int) PyUnstable_TryIncRef(PyObject *);
 PyAPI_FUNC(void) PyUnstable_EnableTryIncRef(PyObject *);
 
 PyAPI_FUNC(int) PyUnstable_Object_IsUniquelyReferenced(PyObject *);
+
+PyAPI_FUNC(int) PyUnstable_SetImmortal(PyObject *op);
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-11-13-30-11.gh-issue-143300.yjB63-.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-11-13-30-11.gh-issue-143300.yjB63-.rst
new file mode 100644
index 00000000000000..85c75a224e42fc
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-11-13-30-11.gh-issue-143300.yjB63-.rst
@@ -0,0 +1 @@
+Add :c:func:`PyUnstable_SetImmortal` C-API function to mark objects as 
:term:`immortal`.
diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c
index 153b28e5fe2e95..9160005e00654f 100644
--- a/Modules/_testcapi/object.c
+++ b/Modules/_testcapi/object.c
@@ -201,6 +201,44 @@ test_py_try_inc_ref(PyObject *self, PyObject *unused)
     Py_RETURN_NONE;
 }
 
+static PyObject *
+test_py_set_immortal(PyObject *self, PyObject *unused)
+{
+    // the object is allocated on C stack as otherwise,
+    // it would trip the refleak checker when the object
+    // is made immortal and leak memory, for the same
+    // reason we cannot call PyObject_Init() on it.
+    PyObject object = {0};
+#ifdef Py_GIL_DISABLED
+    object.ob_tid = _Py_ThreadId();
+    object.ob_gc_bits = 0;
+    object.ob_ref_local = 1;
+    object.ob_ref_shared = 0;
+#else
+    object.ob_refcnt = 1;
+#endif
+    object.ob_type = &PyBaseObject_Type;
+
+    assert(!PyUnstable_IsImmortal(&object));
+    int rc = PyUnstable_SetImmortal(&object);
+    assert(rc == 1);
+    assert(PyUnstable_IsImmortal(&object));
+    Py_DECREF(&object);  // should not dealloc
+    assert(PyUnstable_IsImmortal(&object));
+
+    // Check already immortal object
+    rc = PyUnstable_SetImmortal(&object);
+    assert(rc == 0);
+
+    // Check unicode objects
+    PyObject *unicode = PyUnicode_FromString("test");
+    assert(!PyUnstable_IsImmortal(unicode));
+    rc = PyUnstable_SetImmortal(unicode);
+    assert(rc == 0);
+    assert(!PyUnstable_IsImmortal(unicode));
+    Py_DECREF(unicode);
+    Py_RETURN_NONE;
+}
 
 static PyObject *
 _test_incref(PyObject *ob)
@@ -528,6 +566,7 @@ static PyMethodDef test_methods[] = {
     {"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O},
     {"pyobject_is_unique_temporary_new_object", 
pyobject_is_unique_temporary_new_object, METH_NOARGS},
     {"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS},
+    {"test_py_set_immortal", test_py_set_immortal, METH_NOARGS},
     {"test_xincref_doesnt_leak",test_xincref_doesnt_leak,        METH_NOARGS},
     {"test_incref_doesnt_leak", test_incref_doesnt_leak,         METH_NOARGS},
     {"test_xdecref_doesnt_leak",test_xdecref_doesnt_leak,        METH_NOARGS},
diff --git a/Objects/object.c b/Objects/object.c
index a4f8ddf54b9484..1ddd949d28143e 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2839,6 +2839,17 @@ PyUnstable_EnableTryIncRef(PyObject *op)
 #endif
 }
 
+int
+PyUnstable_SetImmortal(PyObject *op)
+{
+    assert(op != NULL);
+    if (!_PyObject_IsUniquelyReferenced(op) || PyUnicode_Check(op)) {
+        return 0;
+    }
+    _Py_SetImmortal(op);
+    return 1;
+}
+
 void
 _Py_ResurrectReference(PyObject *op)
 {

_______________________________________________
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