summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/weakref.rst16
-rw-r--r--Include/cpython/object.h2
-rw-r--r--Include/internal/pycore_weakref.h2
-rw-r--r--Lib/test/test_capi/test_object.py28
-rw-r--r--Misc/NEWS.d/next/C API/2024-05-08-21-57-50.gh-issue-118789.Ni4UQx.rst2
-rw-r--r--Modules/_testcapi/object.c8
-rw-r--r--Objects/typeobject.c2
-rw-r--r--Objects/weakrefobject.c12
8 files changed, 68 insertions, 4 deletions
diff --git a/Doc/c-api/weakref.rst b/Doc/c-api/weakref.rst
index ae06993..8f233e1 100644
--- a/Doc/c-api/weakref.rst
+++ b/Doc/c-api/weakref.rst
@@ -96,3 +96,19 @@ as much as it can.
This iterates through the weak references for *object* and calls callbacks
for those references which have one. It returns when all callbacks have
been attempted.
+
+
+.. c:function:: void PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *object)
+
+ Clears the weakrefs for *object* without calling the callbacks.
+
+ This function is called by the :c:member:`~PyTypeObject.tp_dealloc` handler
+ for types with finalizers (i.e., :meth:`~object.__del__`). The handler for
+ those objects first calls :c:func:`PyObject_ClearWeakRefs` to clear weakrefs
+ and call their callbacks, then the finalizer, and finally this function to
+ clear any weakrefs that may have been created by the finalizer.
+
+ In most circumstances, it's more appropriate to use
+ :c:func:`PyObject_ClearWeakRefs` to clear weakrefs instead of this function.
+
+ .. versionadded:: 3.13
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index e624326..6cb2c40 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -288,6 +288,8 @@ PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *);
PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *);
PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *);
+PyAPI_FUNC(void) PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *);
+
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
dict as the last parameter. */
PyAPI_FUNC(PyObject *)
diff --git a/Include/internal/pycore_weakref.h b/Include/internal/pycore_weakref.h
index cc6c7ff..94aadb2 100644
--- a/Include/internal/pycore_weakref.h
+++ b/Include/internal/pycore_weakref.h
@@ -109,7 +109,7 @@ extern Py_ssize_t _PyWeakref_GetWeakrefCount(PyObject *obj);
// Clear all the weak references to obj but leave their callbacks uncalled and
// intact.
-extern void _PyWeakref_ClearWeakRefsExceptCallbacks(PyObject *obj);
+extern void _PyWeakref_ClearWeakRefsNoCallbacks(PyObject *obj);
PyAPI_FUNC(int) _PyWeakref_IsDead(PyObject *weakref);
diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py
index fa23bff..cc9c9b6 100644
--- a/Lib/test/test_capi/test_object.py
+++ b/Lib/test/test_capi/test_object.py
@@ -103,5 +103,33 @@ class PrintTest(unittest.TestCase):
with self.assertRaises(OSError):
_testcapi.pyobject_print_os_error(output_filename)
+
+class ClearWeakRefsNoCallbacksTest(unittest.TestCase):
+ """Test PyUnstable_Object_ClearWeakRefsNoCallbacks"""
+ def test_ClearWeakRefsNoCallbacks(self):
+ """Ensure PyUnstable_Object_ClearWeakRefsNoCallbacks works"""
+ import weakref
+ import gc
+ class C:
+ pass
+ obj = C()
+ messages = []
+ ref = weakref.ref(obj, lambda: messages.append("don't add this"))
+ self.assertIs(ref(), obj)
+ self.assertFalse(messages)
+ _testcapi.pyobject_clear_weakrefs_no_callbacks(obj)
+ self.assertIsNone(ref())
+ gc.collect()
+ self.assertFalse(messages)
+
+ def test_ClearWeakRefsNoCallbacks_no_weakref_support(self):
+ """Don't fail on objects that don't support weakrefs"""
+ import weakref
+ obj = object()
+ with self.assertRaises(TypeError):
+ ref = weakref.ref(obj)
+ _testcapi.pyobject_clear_weakrefs_no_callbacks(obj)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/C API/2024-05-08-21-57-50.gh-issue-118789.Ni4UQx.rst b/Misc/NEWS.d/next/C API/2024-05-08-21-57-50.gh-issue-118789.Ni4UQx.rst
new file mode 100644
index 0000000..32a9ec6
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-05-08-21-57-50.gh-issue-118789.Ni4UQx.rst
@@ -0,0 +1,2 @@
+Add :c:func:`PyUnstable_Object_ClearWeakRefsNoCallbacks`, which clears
+weakrefs without calling their callbacks.
diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c
index 8dd34cf..1c76e76 100644
--- a/Modules/_testcapi/object.c
+++ b/Modules/_testcapi/object.c
@@ -117,11 +117,19 @@ pyobject_print_os_error(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
+static PyObject *
+pyobject_clear_weakrefs_no_callbacks(PyObject *self, PyObject *obj)
+{
+ PyUnstable_Object_ClearWeakRefsNoCallbacks(obj);
+ Py_RETURN_NONE;
+}
+
static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
{"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS},
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
+ {"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O},
{NULL},
};
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 5c490e8..5bf2bc2 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -2369,7 +2369,7 @@ subtype_dealloc(PyObject *self)
finalizers since they might rely on part of the object
being finalized that has already been destroyed. */
if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
- _PyWeakref_ClearWeakRefsExceptCallbacks(self);
+ _PyWeakref_ClearWeakRefsNoCallbacks(self);
}
}
diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c
index 3b027e1..0fcd37d 100644
--- a/Objects/weakrefobject.c
+++ b/Objects/weakrefobject.c
@@ -1016,7 +1016,7 @@ PyObject_ClearWeakRefs(PyObject *object)
PyObject *exc = PyErr_GetRaisedException();
PyObject *tuple = PyTuple_New(num_weakrefs * 2);
if (tuple == NULL) {
- _PyWeakref_ClearWeakRefsExceptCallbacks(object);
+ _PyWeakref_ClearWeakRefsNoCallbacks(object);
PyErr_WriteUnraisable(NULL);
PyErr_SetRaisedException(exc);
return;
@@ -1057,6 +1057,14 @@ PyObject_ClearWeakRefs(PyObject *object)
PyErr_SetRaisedException(exc);
}
+void
+PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *obj)
+{
+ if (_PyType_SUPPORTS_WEAKREFS(Py_TYPE(obj))) {
+ _PyWeakref_ClearWeakRefsNoCallbacks(obj);
+ }
+}
+
/* This function is called by _PyStaticType_Dealloc() to clear weak references.
*
* This is called at the end of runtime finalization, so we can just
@@ -1076,7 +1084,7 @@ _PyStaticType_ClearWeakRefs(PyInterpreterState *interp, PyTypeObject *type)
}
void
-_PyWeakref_ClearWeakRefsExceptCallbacks(PyObject *obj)
+_PyWeakref_ClearWeakRefsNoCallbacks(PyObject *obj)
{
/* Modeled after GET_WEAKREFS_LISTPTR().