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]