Skip to content

Commit c00cd86

Browse files
committed
Avoid locking when clearing objects
1 parent 39cd972 commit c00cd86

1 file changed

Lines changed: 51 additions & 29 deletions

File tree

Objects/dictobject.c

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7256,8 +7256,8 @@ decref_maybe_delay(PyObject *obj, bool delay)
72567256
}
72577257
}
72587258

7259-
static int
7260-
set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
7259+
int
7260+
_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
72617261
{
72627262
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
72637263
#ifndef NDEBUG
@@ -7292,8 +7292,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
72927292

72937293
// Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict
72947294
// while the object was locked
7295-
decref_maybe_delay((PyObject *)prev_dict,
7296-
!clear && prev_dict != cur_dict);
7295+
decref_maybe_delay((PyObject *)prev_dict, prev_dict != cur_dict);
72977296
if (err != 0) {
72987297
return err;
72997298
}
@@ -7303,7 +7302,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
73037302

73047303
if (prev_dict != NULL) {
73057304
// decref for the dictionary that we replaced
7306-
decref_maybe_delay((PyObject *)prev_dict, !clear);
7305+
decref_maybe_delay((PyObject *)prev_dict, true);
73077306
}
73087307

73097308
return 0;
@@ -7333,45 +7332,68 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
73337332
(PyDictObject *)Py_XNewRef(new_dict));
73347333

73357334
Py_END_CRITICAL_SECTION();
7336-
decref_maybe_delay((PyObject *)dict, !clear);
7335+
decref_maybe_delay((PyObject *)dict, true);
73377336
}
73387337
assert(_PyObject_InlineValuesConsistencyCheck(obj));
73397338
return err;
73407339
}
73417340

7342-
int
7343-
_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
7344-
{
7345-
return set_or_clear_managed_dict(obj, new_dict, false);
7346-
}
7347-
73487341
void
73497342
PyObject_ClearManagedDict(PyObject *obj)
73507343
{
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 */
7344+
// This is called when the object is being freed and therefore
7345+
// has no other references or during GC when the world is stopped
7346+
// so we don't need any locking.
7347+
if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
73577348
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();
7349+
if (dict == NULL) {
7350+
// We have no materialized dictionary and inline values
7351+
// that just need to be cleared.
7352+
PyDictValues *values = _PyObject_InlineValues(obj);
7353+
if (values->valid) {
7354+
values->valid = 0;
7355+
for (Py_ssize_t i = 0; i < values->capacity; i++) {
7356+
Py_CLEAR(values->values[i]);
7357+
}
7358+
}
7359+
// No dict to clear, we're done
7360+
return;
7361+
} else if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) ==
7362+
_PyObject_InlineValues(obj)) {
7363+
// We have a materialized object which points at the inline
7364+
// values. We need to materialize the keys. Nothing can modify
7365+
// this object, but we need to lock the dictionary.
7366+
int err;
7367+
Py_BEGIN_CRITICAL_SECTION(dict);
7368+
err = _PyDict_DetachFromObject(dict, obj);
7369+
Py_END_CRITICAL_SECTION();
7370+
7371+
if (err) {
7372+
/* Must be out of memory */
7373+
assert(PyErr_Occurred() == PyExc_MemoryError);
7374+
PyErr_FormatUnraisable("Exception ignored while "
7375+
"clearing an object managed dict");
7376+
/* Clear the dict */
7377+
Py_BEGIN_CRITICAL_SECTION2(dict, obj);
7378+
PyInterpreterState *interp = _PyInterpreterState_GET();
7379+
PyDictKeysObject *oldkeys = dict->ma_keys;
7380+
set_keys(dict, Py_EMPTY_KEYS);
7381+
dict->ma_values = NULL;
7382+
dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict));
7383+
STORE_USED(dict, 0);
7384+
set_dict_inline_values(obj, NULL);
7385+
Py_END_CRITICAL_SECTION2();
7386+
}
7387+
}
7388+
// Else we have a materialized dict which doesn't point at the inline
7389+
// values, we can just clear it.
73687390
}
7391+
Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);
73697392
}
73707393

73717394
int
73727395
_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
73737396
{
7374-
ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj);
73757397
assert(_PyObject_ManagedDictPointer(obj)->dict == mp);
73767398
assert(_PyObject_InlineValuesConsistencyCheck(obj));
73777399

0 commit comments

Comments
 (0)