Skip to content
Open
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
16 changes: 16 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -801,3 +801,19 @@ 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 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.
Comment on lines +811 to +812
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we also need to note that op will be GC-untracked if it's tracked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it the same as never garbage collected?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's immediately clear. Something can be "never garbage collected" while still being GC-tracked without being immortal (such as with a reference leak). It'd be nice for users to know that they don't need to call PyObject_GC_UnTrack themselves.


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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, but it's likely worth mentioning that this is useful for avoiding reference count contention.

.. 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);
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