Skip to content

Commit b275b8f

Browse files
gh-133140: Add PyUnstable_Object_IsUniquelyReferenced for free-threading (#133144)
1 parent 0eeaa0e commit b275b8f

File tree

8 files changed

+61
-1
lines changed

8 files changed

+61
-1
lines changed

Doc/c-api/object.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,3 +737,21 @@ Object Protocol
737737
caller must hold a :term:`strong reference` to *obj* when calling this.
738738
739739
.. versionadded:: 3.14
740+
741+
.. c:function:: int PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
742+
743+
Determine if *op* only has one reference.
744+
745+
On GIL-enabled builds, this function is equivalent to
746+
:c:expr:`Py_REFCNT(op) == 1`.
747+
748+
On a :term:`free threaded <free threading>` build, this checks if *op*'s
749+
:term:`reference count` is equal to one and additionally checks if *op*
750+
is only used by this thread. :c:expr:`Py_REFCNT(op) == 1` is **not**
751+
thread-safe on free threaded builds; prefer this function.
752+
753+
The caller must hold an :term:`attached thread state`, despite the fact
754+
that this function doesn't call into the Python interpreter. This function
755+
cannot fail.
756+
757+
.. versionadded:: 3.14

Doc/c-api/refcounting.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ of Python objects.
2323
2424
Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count.
2525
26-
See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
26+
.. note::
27+
28+
On :term:`free threaded <free threading>` builds of Python, returning 1
29+
isn't sufficient to determine if it's safe to treat *o* as having no
30+
access by other threads. Use :c:func:`PyUnstable_Object_IsUniquelyReferenced`
31+
for that instead.
32+
33+
See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`.
2734
2835
.. versionchanged:: 3.10
2936
:c:func:`Py_REFCNT()` is changed to the inline static function.

Doc/whatsnew/3.14.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2469,6 +2469,10 @@ New features
24692469
be used in some cases as a replacement for checking if :c:func:`Py_REFCNT`
24702470
is ``1`` for Python objects passed as arguments to C API functions.
24712471

2472+
* Add :c:func:`PyUnstable_Object_IsUniquelyReferenced` as a replacement for
2473+
``Py_REFCNT(op) == 1`` on :term:`free threaded <free threading>` builds.
2474+
(Contributed by Peter Bierma in :gh:`133140`.)
2475+
24722476

24732477
Limited C API changes
24742478
---------------------

Include/cpython/object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,5 @@ PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);
501501
// before calling this function in order to avoid spurious failures.
502502
PyAPI_FUNC(int) PyUnstable_TryIncRef(PyObject *);
503503
PyAPI_FUNC(void) PyUnstable_EnableTryIncRef(PyObject *);
504+
505+
PyAPI_FUNC(int) PyUnstable_Object_IsUniquelyReferenced(PyObject *);

Lib/test/test_capi/test_object.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ def silly_func(obj):
174174
self.assertTrue(_testinternalcapi.has_deferred_refcount(silly_list))
175175

176176

177+
class IsUniquelyReferencedTest(unittest.TestCase):
178+
"""Test PyUnstable_Object_IsUniquelyReferenced"""
179+
def test_is_uniquely_referenced(self):
180+
self.assertTrue(_testcapi.is_uniquely_referenced(object()))
181+
self.assertTrue(_testcapi.is_uniquely_referenced([]))
182+
# Immortals
183+
self.assertFalse(_testcapi.is_uniquely_referenced("spanish inquisition"))
184+
self.assertFalse(_testcapi.is_uniquely_referenced(42))
185+
# CRASHES is_uniquely_referenced(NULL)
186+
177187
class CAPITest(unittest.TestCase):
178188
def check_negative_refcount(self, code):
179189
# bpo-35059: Check that Py_DECREF() reports the correct filename
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyUnstable_Object_IsUniquelyReferenced` as a replacement for
2+
``Py_REFNCT(op) == 1`` on :term:`free threaded <free threading>`
3+
builds of Python.

Modules/_testcapi/object.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,13 @@ clear_managed_dict(PyObject *self, PyObject *obj)
478478
}
479479

480480

481+
static PyObject *
482+
is_uniquely_referenced(PyObject *self, PyObject *op)
483+
{
484+
return PyBool_FromLong(PyUnstable_Object_IsUniquelyReferenced(op));
485+
}
486+
487+
481488
static PyMethodDef test_methods[] = {
482489
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
483490
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
@@ -503,6 +510,7 @@ static PyMethodDef test_methods[] = {
503510
{"test_py_is_macros", test_py_is_macros, METH_NOARGS},
504511
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
505512
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
513+
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
506514
{NULL},
507515
};
508516

Objects/object.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3275,3 +3275,11 @@ PyUnstable_IsImmortal(PyObject *op)
32753275
assert(op != NULL);
32763276
return _Py_IsImmortal(op);
32773277
}
3278+
3279+
int
3280+
PyUnstable_Object_IsUniquelyReferenced(PyObject *op)
3281+
{
3282+
_Py_AssertHoldsTstate();
3283+
assert(op != NULL);
3284+
return _PyObject_IsUniquelyReferenced(op);
3285+
}

0 commit comments

Comments
 (0)