Skip to content

Commit e8752d7

Browse files
colesburyencukou
andauthored
gh-118789: Add PyUnstable_Object_ClearWeakRefsNoCallbacks (#118807)
This exposes `PyUnstable_Object_ClearWeakRefsNoCallbacks` as an unstable C-API function to provide a thread-safe mechanism for clearing weakrefs without executing callbacks. Some C-API extensions need to clear weakrefs without calling callbacks, such as after running finalizers like we do in subtype_dealloc. Previously they could use `_PyWeakref_ClearRef` on each weakref, but that's not thread-safe in the free-threaded build. Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent 360f14a commit e8752d7

File tree

8 files changed

+68
-4
lines changed

8 files changed

+68
-4
lines changed

Doc/c-api/weakref.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,19 @@ as much as it can.
9696
This iterates through the weak references for *object* and calls callbacks
9797
for those references which have one. It returns when all callbacks have
9898
been attempted.
99+
100+
101+
.. c:function:: void PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *object)
102+
103+
Clears the weakrefs for *object* without calling the callbacks.
104+
105+
This function is called by the :c:member:`~PyTypeObject.tp_dealloc` handler
106+
for types with finalizers (i.e., :meth:`~object.__del__`). The handler for
107+
those objects first calls :c:func:`PyObject_ClearWeakRefs` to clear weakrefs
108+
and call their callbacks, then the finalizer, and finally this function to
109+
clear any weakrefs that may have been created by the finalizer.
110+
111+
In most circumstances, it's more appropriate to use
112+
:c:func:`PyObject_ClearWeakRefs` to clear weakrefs instead of this function.
113+
114+
.. versionadded:: 3.13

Include/cpython/object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *);
288288
PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *);
289289
PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *);
290290

291+
PyAPI_FUNC(void) PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *);
292+
291293
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
292294
dict as the last parameter. */
293295
PyAPI_FUNC(PyObject *)

Include/internal/pycore_weakref.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ extern Py_ssize_t _PyWeakref_GetWeakrefCount(PyObject *obj);
109109

110110
// Clear all the weak references to obj but leave their callbacks uncalled and
111111
// intact.
112-
extern void _PyWeakref_ClearWeakRefsExceptCallbacks(PyObject *obj);
112+
extern void _PyWeakref_ClearWeakRefsNoCallbacks(PyObject *obj);
113113

114114
PyAPI_FUNC(int) _PyWeakref_IsDead(PyObject *weakref);
115115

Lib/test/test_capi/test_object.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,33 @@ def testPyObjectPrintOSError(self):
103103
with self.assertRaises(OSError):
104104
_testcapi.pyobject_print_os_error(output_filename)
105105

106+
107+
class ClearWeakRefsNoCallbacksTest(unittest.TestCase):
108+
"""Test PyUnstable_Object_ClearWeakRefsNoCallbacks"""
109+
def test_ClearWeakRefsNoCallbacks(self):
110+
"""Ensure PyUnstable_Object_ClearWeakRefsNoCallbacks works"""
111+
import weakref
112+
import gc
113+
class C:
114+
pass
115+
obj = C()
116+
messages = []
117+
ref = weakref.ref(obj, lambda: messages.append("don't add this"))
118+
self.assertIs(ref(), obj)
119+
self.assertFalse(messages)
120+
_testcapi.pyobject_clear_weakrefs_no_callbacks(obj)
121+
self.assertIsNone(ref())
122+
gc.collect()
123+
self.assertFalse(messages)
124+
125+
def test_ClearWeakRefsNoCallbacks_no_weakref_support(self):
126+
"""Don't fail on objects that don't support weakrefs"""
127+
import weakref
128+
obj = object()
129+
with self.assertRaises(TypeError):
130+
ref = weakref.ref(obj)
131+
_testcapi.pyobject_clear_weakrefs_no_callbacks(obj)
132+
133+
106134
if __name__ == "__main__":
107135
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyUnstable_Object_ClearWeakRefsNoCallbacks`, which clears
2+
weakrefs without calling their callbacks.

Modules/_testcapi/object.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,19 @@ pyobject_print_os_error(PyObject *self, PyObject *args)
117117
Py_RETURN_NONE;
118118
}
119119

120+
static PyObject *
121+
pyobject_clear_weakrefs_no_callbacks(PyObject *self, PyObject *obj)
122+
{
123+
PyUnstable_Object_ClearWeakRefsNoCallbacks(obj);
124+
Py_RETURN_NONE;
125+
}
126+
120127
static PyMethodDef test_methods[] = {
121128
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
122129
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
123130
{"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS},
124131
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
132+
{"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O},
125133

126134
{NULL},
127135
};

Objects/typeobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2533,7 +2533,7 @@ subtype_dealloc(PyObject *self)
25332533
finalizers since they might rely on part of the object
25342534
being finalized that has already been destroyed. */
25352535
if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
2536-
_PyWeakref_ClearWeakRefsExceptCallbacks(self);
2536+
_PyWeakref_ClearWeakRefsNoCallbacks(self);
25372537
}
25382538
}
25392539

Objects/weakrefobject.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ PyObject_ClearWeakRefs(PyObject *object)
10161016
PyObject *exc = PyErr_GetRaisedException();
10171017
PyObject *tuple = PyTuple_New(num_weakrefs * 2);
10181018
if (tuple == NULL) {
1019-
_PyWeakref_ClearWeakRefsExceptCallbacks(object);
1019+
_PyWeakref_ClearWeakRefsNoCallbacks(object);
10201020
PyErr_WriteUnraisable(NULL);
10211021
PyErr_SetRaisedException(exc);
10221022
return;
@@ -1057,6 +1057,14 @@ PyObject_ClearWeakRefs(PyObject *object)
10571057
PyErr_SetRaisedException(exc);
10581058
}
10591059

1060+
void
1061+
PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *obj)
1062+
{
1063+
if (_PyType_SUPPORTS_WEAKREFS(Py_TYPE(obj))) {
1064+
_PyWeakref_ClearWeakRefsNoCallbacks(obj);
1065+
}
1066+
}
1067+
10601068
/* This function is called by _PyStaticType_Dealloc() to clear weak references.
10611069
*
10621070
* This is called at the end of runtime finalization, so we can just
@@ -1076,7 +1084,7 @@ _PyStaticType_ClearWeakRefs(PyInterpreterState *interp, PyTypeObject *type)
10761084
}
10771085

10781086
void
1079-
_PyWeakref_ClearWeakRefsExceptCallbacks(PyObject *obj)
1087+
_PyWeakref_ClearWeakRefsNoCallbacks(PyObject *obj)
10801088
{
10811089
/* Modeled after GET_WEAKREFS_LISTPTR().
10821090

0 commit comments

Comments
 (0)