Skip to content

Commit 6c450f4

Browse files
authored
gh-130313: Avoid locking when clearing objects (#130126)
Avoid locking when clearing objects in the free-threaded build
1 parent 69426fc commit 6c450f4

File tree

1 file changed

+73
-44
lines changed

1 file changed

+73
-44
lines changed

Objects/dictobject.c

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7163,6 +7163,17 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
71637163
return 0;
71647164
}
71657165

7166+
static void
7167+
clear_inline_values(PyDictValues *values)
7168+
{
7169+
if (values->valid) {
7170+
FT_ATOMIC_STORE_UINT8(values->valid, 0);
7171+
for (Py_ssize_t i = 0; i < values->capacity; i++) {
7172+
Py_CLEAR(values->values[i]);
7173+
}
7174+
}
7175+
}
7176+
71667177
static void
71677178
set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
71687179
{
@@ -7173,12 +7184,7 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
71737184
Py_XINCREF(new_dict);
71747185
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, new_dict);
71757186

7176-
if (values->valid) {
7177-
FT_ATOMIC_STORE_UINT8(values->valid, 0);
7178-
for (Py_ssize_t i = 0; i < values->capacity; i++) {
7179-
Py_CLEAR(values->values[i]);
7180-
}
7181-
}
7187+
clear_inline_values(values);
71827188
}
71837189

71847190
#ifdef Py_GIL_DISABLED
@@ -7256,8 +7262,8 @@ decref_maybe_delay(PyObject *obj, bool delay)
72567262
}
72577263
}
72587264

7259-
static int
7260-
set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
7265+
int
7266+
_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
72617267
{
72627268
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
72637269
#ifndef NDEBUG
@@ -7292,8 +7298,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
72927298

72937299
// Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict
72947300
// while the object was locked
7295-
decref_maybe_delay((PyObject *)prev_dict,
7296-
!clear && prev_dict != cur_dict);
7301+
decref_maybe_delay((PyObject *)prev_dict, prev_dict != cur_dict);
72977302
if (err != 0) {
72987303
return err;
72997304
}
@@ -7303,7 +7308,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
73037308

73047309
if (prev_dict != NULL) {
73057310
// decref for the dictionary that we replaced
7306-
decref_maybe_delay((PyObject *)prev_dict, !clear);
7311+
decref_maybe_delay((PyObject *)prev_dict, true);
73077312
}
73087313

73097314
return 0;
@@ -7333,45 +7338,15 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
73337338
(PyDictObject *)Py_XNewRef(new_dict));
73347339

73357340
Py_END_CRITICAL_SECTION();
7336-
decref_maybe_delay((PyObject *)dict, !clear);
7341+
decref_maybe_delay((PyObject *)dict, true);
73377342
}
73387343
assert(_PyObject_InlineValuesConsistencyCheck(obj));
73397344
return err;
73407345
}
73417346

7342-
int
7343-
_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7344-
{
7345-
return set_or_clear_managed_dict(obj, new_dict, false);
7346-
}
7347-
7348-
void
7349-
PyObject_ClearManagedDict(PyObject *obj)
7350-
{
7351-
if (set_or_clear_managed_dict(obj, NULL, true) < 0) {
7352-
/* Must be out of memory */
7353-
assert(PyErr_Occurred() == PyExc_MemoryError);
7354-
PyErr_FormatUnraisable("Exception ignored while "
7355-
"clearing an object managed dict");
7356-
/* Clear the dict */
7357-
PyDictObject *dict = _PyObject_GetManagedDict(obj);
7358-
Py_BEGIN_CRITICAL_SECTION2(dict, obj);
7359-
dict = _PyObject_ManagedDictPointer(obj)->dict;
7360-
PyInterpreterState *interp = _PyInterpreterState_GET();
7361-
PyDictKeysObject *oldkeys = dict->ma_keys;
7362-
set_keys(dict, Py_EMPTY_KEYS);
7363-
dict->ma_values = NULL;
7364-
dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict));
7365-
STORE_USED(dict, 0);
7366-
set_dict_inline_values(obj, NULL);
7367-
Py_END_CRITICAL_SECTION2();
7368-
}
7369-
}
7370-
7371-
int
7372-
_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
7347+
static int
7348+
detach_dict_from_object(PyDictObject *mp, PyObject *obj)
73737349
{
7374-
ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
73757350
assert(_PyObject_ManagedDictPointer(obj)->dict == mp);
73767351
assert(_PyObject_InlineValuesConsistencyCheck(obj));
73777352

@@ -7401,6 +7376,60 @@ _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
74017376
return 0;
74027377
}
74037378

7379+
7380+
void
7381+
PyObject_ClearManagedDict(PyObject *obj)
7382+
{
7383+
// This is called when the object is being freed or cleared
7384+
// by the GC and therefore known to have no references.
7385+
if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
7386+
PyDictObject *dict = _PyObject_GetManagedDict(obj);
7387+
if (dict == NULL) {
7388+
// We have no materialized dictionary and inline values
7389+
// that just need to be cleared.
7390+
// No dict to clear, we're done
7391+
clear_inline_values(_PyObject_InlineValues(obj));
7392+
return;
7393+
}
7394+
else if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) ==
7395+
_PyObject_InlineValues(obj)) {
7396+
// We have a materialized object which points at the inline
7397+
// values. We need to materialize the keys. Nothing can modify
7398+
// this object, but we need to lock the dictionary.
7399+
int err;
7400+
Py_BEGIN_CRITICAL_SECTION(dict);
7401+
err = detach_dict_from_object(dict, obj);
7402+
Py_END_CRITICAL_SECTION();
7403+
7404+
if (err) {
7405+
/* Must be out of memory */
7406+
assert(PyErr_Occurred() == PyExc_MemoryError);
7407+
PyErr_FormatUnraisable("Exception ignored while "
7408+
"clearing an object managed dict");
7409+
/* Clear the dict */
7410+
Py_BEGIN_CRITICAL_SECTION(dict);
7411+
PyInterpreterState *interp = _PyInterpreterState_GET();
7412+
PyDictKeysObject *oldkeys = dict->ma_keys;
7413+
set_keys(dict, Py_EMPTY_KEYS);
7414+
dict->ma_values = NULL;
7415+
dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict));
7416+
STORE_USED(dict, 0);
7417+
clear_inline_values(_PyObject_InlineValues(obj));
7418+
Py_END_CRITICAL_SECTION();
7419+
}
7420+
}
7421+
}
7422+
Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);
7423+
}
7424+
7425+
int
7426+
_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
7427+
{
7428+
ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
7429+
7430+
return detach_dict_from_object(mp, obj);
7431+
}
7432+
74047433
static inline PyObject *
74057434
ensure_managed_dict(PyObject *obj)
74067435
{

0 commit comments

Comments
 (0)