diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index eedeb180c6b7606..b1701db2e5d930a 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -812,9 +812,12 @@ Object Protocol .. 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. + Marks the object *op* :term:`immortal`. To successfully immortalize an object on a + :term:`free-threaded build` it must have been created by the calling thread. + Unicode objects are not immortalized and :c:func:`PyUnicode_InternInPlace` should + be used instead. + This function is intended to be used for reducing reference counting contention + in free-threaded builds 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 @@ -822,7 +825,8 @@ Object Protocol 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. + Returns 1 if the object was made immortal or is already immortal and returns 0 + if it was not. This function cannot fail. .. versionadded:: 3.15 diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 09a548fd2e24489..8913e9f9f8ec2ef 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -212,10 +212,10 @@ test_py_set_immortal(PyObject *self, PyObject *unused) #ifdef Py_GIL_DISABLED object.ob_tid = _Py_ThreadId(); object.ob_gc_bits = 0; - object.ob_ref_local = 1; - object.ob_ref_shared = 0; + object.ob_ref_local = 3; + object.ob_ref_shared = 128; #else - object.ob_refcnt = 1; + object.ob_refcnt = 3; #endif object.ob_type = &PyBaseObject_Type; @@ -227,8 +227,18 @@ test_py_set_immortal(PyObject *self, PyObject *unused) assert(PyUnstable_IsImmortal(&object)); // Check already immortal object + rc = PyUnstable_SetImmortal(&object); + assert(rc == 1); + + // If not owned by the current thread cannot immortalize + #if Py_GIL_DISABLED + object.ob_tid = _Py_ThreadId() + 1; + object.ob_ref_local = 1; // reset refcount + rc = PyUnstable_SetImmortal(&object); assert(rc == 0); + assert(!PyUnstable_IsImmortal(&object)); + #endif // Check unicode objects PyObject *unicode = PyUnicode_FromString("test"); diff --git a/Objects/object.c b/Objects/object.c index e0e26bb50d36537..73d7c3749ac1933 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2885,7 +2885,17 @@ int PyUnstable_SetImmortal(PyObject *op) { assert(op != NULL); - if (!_PyObject_IsUniquelyReferenced(op) || PyUnicode_Check(op)) { + if (_Py_IsImmortal(op)) { + // If the object is immortal, be idempotent. + return 1; + } +#if Py_GIL_DISABLED + // Only the owning thread can immortalize the object safely + if (!_Py_IsOwnedByCurrentThread(op)) { + return 0; + } +#endif + if (PyUnicode_Check(op)) { return 0; } _Py_SetImmortal(op);