https://github.com/python/cpython/commit/6c450f44c283c61d0e1ada05ead9524a1fe97962
commit: 6c450f44c283c61d0e1ada05ead9524a1fe97962
branch: main
author: Dino Viehland <dinoviehl...@meta.com>
committer: DinoV <dinoviehl...@gmail.com>
date: 2025-02-20T13:32:57-08:00
summary:

gh-130313: Avoid locking when clearing objects (#130126)

Avoid locking when clearing objects in the free-threaded build

files:
M Objects/dictobject.c

diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 900d001d4dd56a..e30d626439e7f8 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -7163,6 +7163,17 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc 
visit, void *arg)
     return 0;
 }
 
+static void
+clear_inline_values(PyDictValues *values)
+{
+    if (values->valid) {
+        FT_ATOMIC_STORE_UINT8(values->valid, 0);
+        for (Py_ssize_t i = 0; i < values->capacity; i++) {
+            Py_CLEAR(values->values[i]);
+        }
+    }
+}
+
 static void
 set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
 {
@@ -7173,12 +7184,7 @@ set_dict_inline_values(PyObject *obj, PyDictObject 
*new_dict)
     Py_XINCREF(new_dict);
     FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, new_dict);
 
-    if (values->valid) {
-        FT_ATOMIC_STORE_UINT8(values->valid, 0);
-        for (Py_ssize_t i = 0; i < values->capacity; i++) {
-            Py_CLEAR(values->values[i]);
-        }
-    }
+    clear_inline_values(values);
 }
 
 #ifdef Py_GIL_DISABLED
@@ -7256,8 +7262,8 @@ decref_maybe_delay(PyObject *obj, bool delay)
     }
 }
 
-static int
-set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
+int
+_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
 {
     assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
 #ifndef NDEBUG
@@ -7292,8 +7298,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject 
*new_dict, bool clear)
 
             // Decref for the dictionary we incref'd in 
try_set_dict_inline_only_or_other_dict
             // while the object was locked
-            decref_maybe_delay((PyObject *)prev_dict,
-                               !clear && prev_dict != cur_dict);
+            decref_maybe_delay((PyObject *)prev_dict, prev_dict != cur_dict);
             if (err != 0) {
                 return err;
             }
@@ -7303,7 +7308,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject 
*new_dict, bool clear)
 
         if (prev_dict != NULL) {
             // decref for the dictionary that we replaced
-            decref_maybe_delay((PyObject *)prev_dict, !clear);
+            decref_maybe_delay((PyObject *)prev_dict, true);
         }
 
         return 0;
@@ -7333,45 +7338,15 @@ set_or_clear_managed_dict(PyObject *obj, PyObject 
*new_dict, bool clear)
                             (PyDictObject *)Py_XNewRef(new_dict));
 
         Py_END_CRITICAL_SECTION();
-        decref_maybe_delay((PyObject *)dict, !clear);
+        decref_maybe_delay((PyObject *)dict, true);
     }
     assert(_PyObject_InlineValuesConsistencyCheck(obj));
     return err;
 }
 
-int
-_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
-{
-    return set_or_clear_managed_dict(obj, new_dict, false);
-}
-
-void
-PyObject_ClearManagedDict(PyObject *obj)
-{
-    if (set_or_clear_managed_dict(obj, NULL, true) < 0) {
-        /* Must be out of memory */
-        assert(PyErr_Occurred() == PyExc_MemoryError);
-        PyErr_FormatUnraisable("Exception ignored while "
-                               "clearing an object managed dict");
-        /* Clear the dict */
-        PyDictObject *dict = _PyObject_GetManagedDict(obj);
-        Py_BEGIN_CRITICAL_SECTION2(dict, obj);
-        dict = _PyObject_ManagedDictPointer(obj)->dict;
-        PyInterpreterState *interp = _PyInterpreterState_GET();
-        PyDictKeysObject *oldkeys = dict->ma_keys;
-        set_keys(dict, Py_EMPTY_KEYS);
-        dict->ma_values = NULL;
-        dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict));
-        STORE_USED(dict, 0);
-        set_dict_inline_values(obj, NULL);
-        Py_END_CRITICAL_SECTION2();
-    }
-}
-
-int
-_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
+static int
+detach_dict_from_object(PyDictObject *mp, PyObject *obj)
 {
-    ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
     assert(_PyObject_ManagedDictPointer(obj)->dict == mp);
     assert(_PyObject_InlineValuesConsistencyCheck(obj));
 
@@ -7401,6 +7376,60 @@ _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
     return 0;
 }
 
+
+void
+PyObject_ClearManagedDict(PyObject *obj)
+{
+    // This is called when the object is being freed or cleared
+    // by the GC and therefore known to have no references.
+    if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
+        PyDictObject *dict = _PyObject_GetManagedDict(obj);
+        if (dict == NULL) {
+            // We have no materialized dictionary and inline values
+            // that just need to be cleared.
+            // No dict to clear, we're done
+            clear_inline_values(_PyObject_InlineValues(obj));
+            return;
+        }
+        else if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) ==
+                    _PyObject_InlineValues(obj)) {
+            // We have a materialized object which points at the inline
+            // values. We need to materialize the keys. Nothing can modify
+            // this object, but we need to lock the dictionary.
+            int err;
+            Py_BEGIN_CRITICAL_SECTION(dict);
+            err = detach_dict_from_object(dict, obj);
+            Py_END_CRITICAL_SECTION();
+
+            if (err) {
+                /* Must be out of memory */
+                assert(PyErr_Occurred() == PyExc_MemoryError);
+                PyErr_FormatUnraisable("Exception ignored while "
+                                       "clearing an object managed dict");
+                /* Clear the dict */
+                Py_BEGIN_CRITICAL_SECTION(dict);
+                PyInterpreterState *interp = _PyInterpreterState_GET();
+                PyDictKeysObject *oldkeys = dict->ma_keys;
+                set_keys(dict, Py_EMPTY_KEYS);
+                dict->ma_values = NULL;
+                dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict));
+                STORE_USED(dict, 0);
+                clear_inline_values(_PyObject_InlineValues(obj));
+                Py_END_CRITICAL_SECTION();
+            }
+        }
+    }
+    Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);
+}
+
+int
+_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
+{
+    ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
+
+    return detach_dict_from_object(mp, obj);
+}
+
 static inline PyObject *
 ensure_managed_dict(PyObject *obj)
 {

_______________________________________________
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