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
17 changes: 17 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :c:func:`PyUnstable_SetImmortal` C-API function to mark objects as :term:`immortal`.
39 changes: 39 additions & 0 deletions Modules/_testcapi/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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},
Expand Down
11 changes: 11 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Loading