]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-143300: implement `PyUnstable_SetImmortal` for marking objects as immortal (#144543)
authorKumar Aditya <kumaraditya@python.org>
Wed, 11 Feb 2026 15:29:31 +0000 (20:59 +0530)
committerGitHub <noreply@github.com>
Wed, 11 Feb 2026 15:29:31 +0000 (20:59 +0530)
Doc/c-api/object.rst
Include/cpython/object.h
Misc/NEWS.d/next/Core_and_Builtins/2026-02-11-13-30-11.gh-issue-143300.yjB63-.rst [new file with mode: 0644]
Modules/_testcapi/object.c
Objects/object.c

index 992a4383f972413af7feb84cbeddb37b790f1985..f71bfebdb2a19a45509dd979c87ddf5854a8252b 100644 (file)
@@ -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
index 28c909531dba64bbe3bb2f60ff897d9cdf1ab81f..61cdb93d1d535428a969b75714dacbf6f387e0d1 100644 (file)
@@ -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 (file)
index 0000000..85c75a2
--- /dev/null
@@ -0,0 +1 @@
+Add :c:func:`PyUnstable_SetImmortal` C-API function to mark objects as :term:`immortal`.
index 153b28e5fe2e959b3d647aac3337e5dde510b379..9160005e00654f997f0386d822ad3fb3a4b26ca1 100644 (file)
@@ -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},
index a4f8ddf54b948487edb472be3202f5126df1c46b..1ddd949d28143ec5795c495d01ec908e1d134f68 100644 (file)
@@ -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)
 {