https://github.com/python/cpython/commit/1ce59849610fe03beebcddfacb3d055a7074ef16
commit: 1ce59849610fe03beebcddfacb3d055a7074ef16
branch: 3.13
author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com>
committer: colesbury <colesb...@gmail.com>
date: 2024-06-18T14:54:51Z
summary:

[3.13] gh-118789: Add `PyUnstable_Object_ClearWeakRefsNoCallbacks` (GH-118807) 
(#120695)

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.

(cherry picked from commit e8752d7b80775ec2a348cd4bf38cbe26a4a07615)

Co-authored-by: Sam Gross <colesb...@gmail.com>
Co-authored-by: Petr Viktorin <encu...@gmail.com>

files:
A Misc/NEWS.d/next/C API/2024-05-08-21-57-50.gh-issue-118789.Ni4UQx.rst
M Doc/c-api/weakref.rst
M Include/cpython/object.h
M Include/internal/pycore_weakref.h
M Lib/test/test_capi/test_object.py
M Modules/_testcapi/object.c
M Objects/typeobject.c
M Objects/weakrefobject.c

diff --git a/Doc/c-api/weakref.rst b/Doc/c-api/weakref.rst
index ae0699383900c4..8f233e16fb17cf 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 e624326693d8e7..6cb2c40fe2eb71 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 cc6c7ff9a9b438..94aadb2c1547dd 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 fa23bff4e98918..cc9c9b688f00e2 100644
--- a/Lib/test/test_capi/test_object.py
+++ b/Lib/test/test_capi/test_object.py
@@ -103,5 +103,33 @@ def testPyObjectPrintOSError(self):
         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 00000000000000..32a9ec6d0710f6
--- /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 8dd34cf4fc47d4..1c76e766a790f0 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 5c490e833739fc..5bf2bc24f316bb 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 3b027e1b518ba6..0fcd37d949b34a 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().
 

_______________________________________________
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

Reply via email to