https://github.com/python/cpython/commit/b275b8f34210bae2fc1e0af23515df5efe911c8e commit: b275b8f34210bae2fc1e0af23515df5efe911c8e branch: main author: Peter Bierma <zintensity...@gmail.com> committer: vstinner <vstin...@python.org> date: 2025-05-05T21:01:20+02:00 summary:
gh-133140: Add `PyUnstable_Object_IsUniquelyReferenced` for free-threading (#133144) files: A Misc/NEWS.d/next/C_API/2025-04-29-06-27-46.gh-issue-133140.IPGGc3.rst M Doc/c-api/object.rst M Doc/c-api/refcounting.rst M Doc/whatsnew/3.14.rst M Include/cpython/object.h M Lib/test/test_capi/test_object.py M Modules/_testcapi/object.c M Objects/object.c diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index efad4d215b1986..0fd159f1eb87f8 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -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 diff --git a/Doc/c-api/refcounting.rst b/Doc/c-api/refcounting.rst index 83febcf70a5548..b23f016f9b0a06 100644 --- a/Doc/c-api/refcounting.rst +++ b/Doc/c-api/refcounting.rst @@ -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. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 795403a9b7c107..851611ad170c91 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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 --------------------- diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 818fc7d05605ff..36000c887b2d9a 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -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 *); diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 54a01ac7c4a7ae..127862546b1bce 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -174,6 +174,16 @@ def silly_func(obj): 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 diff --git a/Misc/NEWS.d/next/C_API/2025-04-29-06-27-46.gh-issue-133140.IPGGc3.rst b/Misc/NEWS.d/next/C_API/2025-04-29-06-27-46.gh-issue-133140.IPGGc3.rst new file mode 100644 index 00000000000000..f73b7de2b5c7c2 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-04-29-06-27-46.gh-issue-133140.IPGGc3.rst @@ -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. diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 5c67adfee29dc1..798ef97c495aeb 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -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}, }; diff --git a/Objects/object.c b/Objects/object.c index f5fe25eb5aaf8f..e9a93f87be4cf7 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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); +} _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com