summaryrefslogtreecommitdiffstats
path: root/Objects/dictobject.c
diff options
context:
space:
mode:
Diffstat (limited to 'Objects/dictobject.c')
-rw-r--r--Objects/dictobject.c164
1 files changed, 132 insertions, 32 deletions
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 23616b3..393e9f9 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -7087,51 +7087,146 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
}
}
-int
-_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
+#ifdef Py_GIL_DISABLED
+
+// Trys and sets the dictionary for an object in the easy case when our current
+// dictionary is either completely not materialized or is a dictionary which
+// does not point at the inline values.
+static bool
+try_set_dict_inline_only_or_other_dict(PyObject *obj, PyObject *new_dict, PyDictObject **cur_dict)
+{
+ bool replaced = false;
+ Py_BEGIN_CRITICAL_SECTION(obj);
+
+ PyDictObject *dict = *cur_dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+ // We only have inline values, we can just completely replace them.
+ set_dict_inline_values(obj, (PyDictObject *)new_dict);
+ replaced = true;
+ goto exit_lock;
+ }
+
+ if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) != _PyObject_InlineValues(obj)) {
+ // We have a materialized dict which doesn't point at the inline values,
+ // We get to simply swap dictionaries and free the old dictionary.
+ FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)Py_XNewRef(new_dict));
+ replaced = true;
+ goto exit_lock;
+ }
+ else {
+ // We have inline values, we need to lock the dict and the object
+ // at the same time to safely dematerialize them. To do that while releasing
+ // the object lock we need a strong reference to the current dictionary.
+ Py_INCREF(dict);
+ }
+exit_lock:
+ Py_END_CRITICAL_SECTION();
+ return replaced;
+}
+
+// Replaces a dictionary that is probably the dictionary which has been
+// materialized and points at the inline values. We could have raced
+// and replaced it with another dictionary though.
+static int
+replace_dict_probably_inline_materialized(PyObject *obj, PyDictObject *inline_dict,
+ PyDictObject *cur_dict, PyObject *new_dict)
+{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
+
+ if (cur_dict == inline_dict) {
+ assert(FT_ATOMIC_LOAD_PTR_RELAXED(inline_dict->ma_values) == _PyObject_InlineValues(obj));
+
+ int err = _PyDict_DetachFromObject(inline_dict, obj);
+ if (err != 0) {
+ assert(new_dict == NULL);
+ return err;
+ }
+ }
+
+ FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)Py_XNewRef(new_dict));
+ return 0;
+}
+
+#endif
+
+static void
+decref_maybe_delay(PyObject *obj, bool delay)
+{
+ if (delay) {
+ _PyObject_XDecRefDelayed(obj);
+ }
+ else {
+ Py_XDECREF(obj);
+ }
+}
+
+static int
+set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
{
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
+#ifndef NDEBUG
+ Py_BEGIN_CRITICAL_SECTION(obj);
assert(_PyObject_InlineValuesConsistencyCheck(obj));
+ Py_END_CRITICAL_SECTION();
+#endif
int err = 0;
PyTypeObject *tp = Py_TYPE(obj);
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
- PyDictObject *dict = _PyObject_GetManagedDict(obj);
- if (dict == NULL) {
#ifdef Py_GIL_DISABLED
- Py_BEGIN_CRITICAL_SECTION(obj);
+ PyDictObject *prev_dict;
+ if (!try_set_dict_inline_only_or_other_dict(obj, new_dict, &prev_dict)) {
+ // We had a materialized dictionary which pointed at the inline
+ // values. We need to lock both the object and the dict at the
+ // same time to safely replace it. We can't merely lock the dictionary
+ // while the object is locked because it could suspend the object lock.
+ PyDictObject *cur_dict;
- dict = _PyObject_ManagedDictPointer(obj)->dict;
- if (dict == NULL) {
- set_dict_inline_values(obj, (PyDictObject *)new_dict);
- }
+ assert(prev_dict != NULL);
+ Py_BEGIN_CRITICAL_SECTION2(obj, prev_dict);
- Py_END_CRITICAL_SECTION();
+ // We could have had another thread race in between the call to
+ // try_set_dict_inline_only_or_other_dict where we locked the object
+ // and when we unlocked and re-locked the dictionary.
+ cur_dict = _PyObject_GetManagedDict(obj);
- if (dict == NULL) {
- return 0;
+ err = replace_dict_probably_inline_materialized(obj, prev_dict,
+ cur_dict, new_dict);
+
+ Py_END_CRITICAL_SECTION2();
+
+ // 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);
+ if (err != 0) {
+ return err;
}
-#else
- set_dict_inline_values(obj, (PyDictObject *)new_dict);
- return 0;
-#endif
- }
- Py_BEGIN_CRITICAL_SECTION2(dict, obj);
+ prev_dict = cur_dict;
+ }
- // We've locked dict, but the actual dict could have changed
- // since we locked it.
- dict = _PyObject_ManagedDictPointer(obj)->dict;
- err = _PyDict_DetachFromObject(dict, obj);
- assert(err == 0 || new_dict == NULL);
- if (err == 0) {
- FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
- (PyDictObject *)Py_XNewRef(new_dict));
+ if (prev_dict != NULL) {
+ // decref for the dictionary that we replaced
+ decref_maybe_delay((PyObject *)prev_dict, !clear);
}
- Py_END_CRITICAL_SECTION2();
- if (err == 0) {
- Py_XDECREF(dict);
+ return 0;
+#else
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+ set_dict_inline_values(obj, (PyDictObject *)new_dict);
+ return 0;
+ }
+ if (_PyDict_DetachFromObject(dict, obj) == 0) {
+ _PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)Py_XNewRef(new_dict);
+ Py_DECREF(dict);
+ return 0;
}
+ assert(new_dict == NULL);
+ return -1;
+#endif
}
else {
PyDictObject *dict;
@@ -7144,17 +7239,22 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
(PyDictObject *)Py_XNewRef(new_dict));
Py_END_CRITICAL_SECTION();
-
- Py_XDECREF(dict);
+ decref_maybe_delay((PyObject *)dict, !clear);
}
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 (_PyObject_SetManagedDict(obj, NULL) < 0) {
+ if (set_or_clear_managed_dict(obj, NULL, true) < 0) {
/* Must be out of memory */
assert(PyErr_Occurred() == PyExc_MemoryError);
PyErr_WriteUnraisable(NULL);