gh-133140: Add PyUnstable_Object_IsUniquelyReferenced for free-threading (#133144)

This commit is contained in:
Peter Bierma 2025-05-05 15:01:20 -04:00 committed by GitHub
parent 0eeaa0ef8b
commit b275b8f342
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 61 additions and 1 deletions

View file

@ -737,3 +737,21 @@ Object Protocol
caller must hold a :term:`strong reference` to *obj* when calling this.
.. versionadded:: 3.14
.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
Determine if *op* only has one reference.
On GIL-enabled builds, this function is equivalent to
:c:expr:`Py_REFCNT(op) == 1`.
On a :term:`free threaded <free threading>` build, this checks if *op*'s
:term:`reference count` is equal to one and additionally checks if *op*
is only used by this thread. :c:expr:`Py_REFCNT(op) == 1` is **not**
thread-safe on free threaded builds; prefer this function.
The caller must hold an :term:`attached thread state`, despite the fact
that this function doesn't call into the Python interpreter. This function
cannot fail.
.. versionadded:: 3.14

View file

@ -23,7 +23,14 @@ of Python objects.
Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count.
See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
.. note::
On :term:`free threaded <free threading>` builds of Python, returning 1
isn't sufficient to determine if it's safe to treat *o* as having no
access by other threads. Use :c:func:`PyUnstable_Object_IsUniquelyReferenced`
for that instead.
See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
.. versionchanged:: 3.10
:c:func:`Py_REFCNT()` is changed to the inline static function.

View file

@ -2469,6 +2469,10 @@ New features
be used in some cases as a replacement for checking if :c:func:`Py_REFCNT`
is ``1`` for Python objects passed as arguments to C API functions.
* Add :c:func:`PyUnstable_Object_IsUniquelyReferenced` as a replacement for
``Py_REFCNT(op) == 1`` on :term:`free threaded <free threading>` builds.
(Contributed by Peter Bierma in :gh:`133140`.)
Limited C API changes
---------------------

View file

@ -501,3 +501,5 @@ PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);
// before calling this function in order to avoid spurious failures.
PyAPI_FUNC(int) PyUnstable_TryIncRef(PyObject *);
PyAPI_FUNC(void) PyUnstable_EnableTryIncRef(PyObject *);
PyAPI_FUNC(int) PyUnstable_Object_IsUniquelyReferenced(PyObject *);

View file

@ -174,6 +174,16 @@ class EnableDeferredRefcountingTest(unittest.TestCase):
self.assertTrue(_testinternalcapi.has_deferred_refcount(silly_list))
class IsUniquelyReferencedTest(unittest.TestCase):
"""Test PyUnstable_Object_IsUniquelyReferenced"""
def test_is_uniquely_referenced(self):
self.assertTrue(_testcapi.is_uniquely_referenced(object()))
self.assertTrue(_testcapi.is_uniquely_referenced([]))
# Immortals
self.assertFalse(_testcapi.is_uniquely_referenced("spanish inquisition"))
self.assertFalse(_testcapi.is_uniquely_referenced(42))
# CRASHES is_uniquely_referenced(NULL)
class CAPITest(unittest.TestCase):
def check_negative_refcount(self, code):
# bpo-35059: Check that Py_DECREF() reports the correct filename

View file

@ -0,0 +1,3 @@
Add :c:func:`PyUnstable_Object_IsUniquelyReferenced` as a replacement for
``Py_REFNCT(op) == 1`` on :term:`free threaded <free threading>`
builds of Python.

View file

@ -478,6 +478,13 @@ clear_managed_dict(PyObject *self, PyObject *obj)
}
static PyObject *
is_uniquely_referenced(PyObject *self, PyObject *op)
{
return PyBool_FromLong(PyUnstable_Object_IsUniquelyReferenced(op));
}
static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
@ -503,6 +510,7 @@ static PyMethodDef test_methods[] = {
{"test_py_is_macros", test_py_is_macros, METH_NOARGS},
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
{NULL},
};

View file

@ -3275,3 +3275,11 @@ PyUnstable_IsImmortal(PyObject *op)
assert(op != NULL);
return _Py_IsImmortal(op);
}
int
PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
{
_Py_AssertHoldsTstate();
assert(op != NULL);
return _PyObject_IsUniquelyReferenced(op);
}