Skip to content

Commit

Permalink
gh-128008: Add PyWeakref_IsDead() (GH-128009)
Browse files Browse the repository at this point in the history
The `PyWeakref_IsDead()` function tests if a weak reference is dead
without any side effects. Although you can also detect if a weak
reference is dead using `PyWeakref_GetRef()`, that function returns a
strong reference that must be `Py_DECREF()`'d, which can introduce side
effects if the last reference is concurrently dropped (at least in the
free threading build).
  • Loading branch information
colesbury authored Dec 19, 2024
1 parent b9b3e4a commit 7b811d0
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Doc/c-api/weakref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ as much as it can.
Use :c:func:`PyWeakref_GetRef` instead.
.. c:function:: int PyWeakref_IsDead(PyObject *ref)
Test if the weak reference *ref* is dead. Returns 1 if the reference is
dead, 0 if it is alive, and -1 with an error set if *ref* is not a weak
reference object.
.. versionadded:: 3.14
.. c:function:: void PyObject_ClearWeakRefs(PyObject *object)
This function is called by the :c:member:`~PyTypeObject.tp_dealloc` handler
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/weakrefobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self);
#define _PyWeakref_CAST(op) \
(assert(PyWeakref_Check(op)), _Py_CAST(PyWeakReference*, (op)))

// Test if a weak reference is dead.
PyAPI_FUNC(int) PyWeakref_IsDead(PyObject *ref);

Py_DEPRECATED(3.13) static inline PyObject* PyWeakref_GET_OBJECT(PyObject *ref_obj)
{
PyWeakReference *ref = _PyWeakref_CAST(ref_obj);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyWeakref_IsDead` function, which tests if a weak reference is
dead.
14 changes: 14 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3144,6 +3144,7 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
PyObject *ref = UNINITIALIZED_PTR;
assert(PyWeakref_GetRef(weakref, &ref) == 1);
assert(ref == obj);
assert(!PyWeakref_IsDead(weakref));
assert(Py_REFCNT(obj) == (refcnt + 1));
Py_DECREF(ref);

Expand All @@ -3159,6 +3160,8 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
assert(Py_REFCNT(obj) == 1);
Py_DECREF(obj);

assert(PyWeakref_IsDead(weakref));

// test PyWeakref_GET_OBJECT(), reference is dead
assert(PyWeakref_GET_OBJECT(weakref) == Py_None);

Expand All @@ -3181,6 +3184,12 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
PyErr_Clear();
assert(ref == NULL);

// test PyWeakRef_IsDead(), invalid type
assert(!PyErr_Occurred());
assert(PyWeakref_IsDead(invalid_weakref) == -1);
assert(PyErr_ExceptionMatches(PyExc_TypeError));
PyErr_Clear();

// test PyWeakref_GetObject(), invalid type
assert(PyWeakref_GetObject(invalid_weakref) == NULL);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
Expand All @@ -3193,6 +3202,11 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
assert(ref == NULL);
PyErr_Clear();

// test PyWeakref_IsDead(NULL)
assert(PyWeakref_IsDead(NULL) == -1);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
PyErr_Clear();

// test PyWeakref_GetObject(NULL)
assert(PyWeakref_GetObject(NULL) == NULL);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
Expand Down
13 changes: 13 additions & 0 deletions Objects/weakrefobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,19 @@ PyWeakref_NewProxy(PyObject *ob, PyObject *callback)
return (PyObject *)get_or_create_weakref(type, ob, callback);
}

int
PyWeakref_IsDead(PyObject *ref)
{
if (ref == NULL) {
PyErr_BadInternalCall();
return -1;
}
if (!PyWeakref_Check(ref)) {
PyErr_Format(PyExc_TypeError, "expected a weakref, got %T", ref);
return -1;
}
return _PyWeakref_IS_DEAD(ref);
}

int
PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
Expand Down

0 comments on commit 7b811d0

Please sign in to comment.