#define PyDict_MINSIZE 8
#include "Python.h"
-#include "pycore_bitutils.h" // _Py_bit_length
-#include "pycore_call.h" // _PyObject_CallNoArgs()
-#include "pycore_ceval.h" // _PyEval_GetBuiltin()
-#include "pycore_code.h" // stats
-#include "pycore_dict.h" // export _PyDict_SizeOf()
-#include "pycore_freelist.h" // _PyFreeListState_GET()
-#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
-#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
-#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
-#include "pycore_pystate.h" // _PyThreadState_GET()
-#include "pycore_setobject.h" // _PySet_NextEntry()
-#include "stringlib/eq.h" // unicode_eq()
+#include "pycore_bitutils.h" // _Py_bit_length
+#include "pycore_call.h" // _PyObject_CallNoArgs()
+#include "pycore_ceval.h" // _PyEval_GetBuiltin()
+#include "pycore_code.h" // stats
+#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION
+#include "pycore_dict.h" // export _PyDict_SizeOf()
+#include "pycore_freelist.h" // _PyFreeListState_GET()
+#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
+#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
+#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
+#include "pycore_pystate.h" // _PyThreadState_GET()
+#include "pycore_setobject.h" // _PySet_NextEntry()
+#include "stringlib/eq.h" // unicode_eq()
#include <stdbool.h>
it's USABLE_FRACTION (currently two-thirds) full.
*/
+#ifdef Py_GIL_DISABLED
+
+static inline void
+ASSERT_DICT_LOCKED(PyObject *op)
+{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+}
+#define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op))
+
+#else
+
+#define ASSERT_DICT_LOCKED(op)
+
+#endif
+
#define PERTURB_SHIFT 5
/*
static PyObject* dict_iter(PyObject *dict);
+static int
+contains_lock_held(PyDictObject *mp, PyObject *key);
+static int
+contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash);
+static int
+setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value);
+static int
+dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value,
+ PyObject **result, int incref_result);
+
#include "clinic/dictobject.c.h"
assert(orig->ma_keys != Py_EMPTY_KEYS);
assert(orig->ma_keys->dk_refcnt == 1);
+ ASSERT_DICT_LOCKED(orig);
+
size_t keys_size = _PyDict_KeysSize(orig->ma_keys);
PyDictKeysObject *keys = PyMem_Malloc(keys_size);
if (keys == NULL) {
{
PyObject *old_value;
+ ASSERT_DICT_LOCKED(mp);
+
if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) {
if (insertion_resize(interp, mp, 0) < 0)
goto Fail;
PyObject *key, Py_hash_t hash, PyObject *value)
{
assert(mp->ma_keys == Py_EMPTY_KEYS);
+ ASSERT_DICT_LOCKED(mp);
uint64_t new_version = _PyDict_NotifyEvent(
interp, PyDict_EVENT_ADDED, mp, key, value);
PyDictKeysObject *oldkeys;
PyDictValues *oldvalues;
+ ASSERT_DICT_LOCKED(mp);
+
if (log2_newsize >= SIZEOF_SIZE_T*8) {
PyErr_NoMemory();
return -1;
for (Py_ssize_t i = 0; i < length; i++) {
PyObject *key = *ks;
PyObject *value = *vs;
- if (PyDict_SetItem(dict, key, value) < 0) {
+ if (setitem_lock_held((PyDictObject *)dict, key, value) < 0) {
Py_DECREF(dict);
return NULL;
}
Py_ssize_t
_PyDict_LookupIndex(PyDictObject *mp, PyObject *key)
{
+ // TODO: Thread safety
PyObject *value;
assert(PyDict_CheckExact((PyObject*)mp));
assert(PyUnicode_CheckExact(key));
}
/* Consumes references to key and value */
-int
-_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value)
+static int
+setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
{
+ ASSERT_DICT_LOCKED(mp);
+
assert(key);
assert(value);
assert(PyDict_Check(mp));
return -1;
}
}
+
PyInterpreterState *interp = _PyInterpreterState_GET();
+
if (mp->ma_keys == Py_EMPTY_KEYS) {
return insert_to_emptydict(interp, mp, key, hash, value);
}
return insertdict(interp, mp, key, hash, value);
}
+int
+_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value)
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(mp);
+ res = setitem_take2_lock_held(mp, key, value);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the
* dictionary if it's merely replacing the value for an existing key.
* This means that it's safe to loop over a dictionary with PyDict_Next()
Py_NewRef(key), Py_NewRef(value));
}
+static int
+setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
+{
+ assert(key);
+ assert(value);
+ return setitem_take2_lock_held(mp,
+ Py_NewRef(key), Py_NewRef(value));
+}
+
+
int
_PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
Py_hash_t hash)
assert(hash != -1);
mp = (PyDictObject *)op;
+ int res;
PyInterpreterState *interp = _PyInterpreterState_GET();
+
+ Py_BEGIN_CRITICAL_SECTION(mp);
+
if (mp->ma_keys == Py_EMPTY_KEYS) {
- return insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
+ res = insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
}
- /* insertdict() handles any resizing that might be necessary */
- return insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
+ else {
+ /* insertdict() handles any resizing that might be necessary */
+ res = insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
+ }
+
+ Py_END_CRITICAL_SECTION();
+ return res;
}
static void
{
PyObject *old_key;
+ ASSERT_DICT_LOCKED(mp);
+
Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix);
assert(hashpos >= 0);
return _PyDict_DelItem_KnownHash(op, key, hash);
}
-int
-_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
+static int
+delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash)
{
Py_ssize_t ix;
PyDictObject *mp;
PyErr_BadInternalCall();
return -1;
}
+
+ ASSERT_DICT_LOCKED(op);
+
assert(key);
assert(hash != -1);
mp = (PyDictObject *)op;
return delitem_common(mp, hash, ix, old_value, new_version);
}
-/* This function promises that the predicate -> deletion sequence is atomic
- * (i.e. protected by the GIL), assuming the predicate itself doesn't
- * release the GIL.
- */
int
-_PyDict_DelItemIf(PyObject *op, PyObject *key,
- int (*predicate)(PyObject *value))
+_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ res = delitem_knownhash_lock_held(op, key, hash);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
+static int
+delitemif_lock_held(PyObject *op, PyObject *key,
+ int (*predicate)(PyObject *value))
{
Py_ssize_t hashpos, ix;
PyDictObject *mp;
PyObject *old_value;
int res;
+ ASSERT_DICT_LOCKED(op);
+
if (!PyDict_Check(op)) {
PyErr_BadInternalCall();
return -1;
return 0;
}
}
+/* This function promises that the predicate -> deletion sequence is atomic
+ * (i.e. protected by the GIL or the per-dict mutex in free threaded builds),
+ * assuming the predicate itself doesn't release the GIL (or cause re-entrancy
+ * which would release the per-dict mutex)
+ */
+int
+_PyDict_DelItemIf(PyObject *op, PyObject *key,
+ int (*predicate)(PyObject *value))
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ res = delitemif_lock_held(op, key, predicate);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
-
-void
-PyDict_Clear(PyObject *op)
+static void
+clear_lock_held(PyObject *op)
{
PyDictObject *mp;
PyDictKeysObject *oldkeys;
PyDictValues *oldvalues;
Py_ssize_t i, n;
+ ASSERT_DICT_LOCKED(op);
+
if (!PyDict_Check(op))
return;
mp = ((PyDictObject *)op);
ASSERT_CONSISTENT(mp);
}
+void
+PyDict_Clear(PyObject *op)
+{
+ Py_BEGIN_CRITICAL_SECTION(op);
+ clear_lock_held(op);
+ Py_END_CRITICAL_SECTION();
+}
+
/* Internal version of PyDict_Next that returns a hash value in addition
* to the key and value.
* Return 1 on success, return 0 when the reached the end of the dictionary
if (!PyDict_Check(op))
return 0;
+
+ ASSERT_DICT_LOCKED(op);
+
mp = (PyDictObject *)op;
i = *ppos;
if (mp->ma_values) {
int
PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue)
{
- return _PyDict_Next(op, ppos, pkey, pvalue, NULL);
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ res = _PyDict_Next(op, ppos, pkey, pvalue, NULL);
+ Py_END_CRITICAL_SECTION();
+ return res;
}
{
assert(PyDict_Check(mp));
+ ASSERT_DICT_LOCKED(mp);
+
if (mp->ma_used == 0) {
if (result) {
*result = NULL;
return 1;
}
-
-int
-PyDict_Pop(PyObject *op, PyObject *key, PyObject **result)
+static int
+pop_lock_held(PyObject *op, PyObject *key, PyObject **result)
{
+ ASSERT_DICT_LOCKED(op);
+
if (!PyDict_Check(op)) {
if (result) {
*result = NULL;
return _PyDict_Pop_KnownHash(dict, key, hash, result);
}
+int
+PyDict_Pop(PyObject *op, PyObject *key, PyObject **result)
+{
+ int err;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ err = pop_lock_held(op, key, result);
+ Py_END_CRITICAL_SECTION();
+
+ return err;
+}
+
int
PyDict_PopString(PyObject *op, const char *key, PyObject **result)
return result;
}
+static PyDictObject *
+dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
+ PyObject *iterable, PyObject *value)
+{
+ PyObject *oldvalue;
+ Py_ssize_t pos = 0;
+ PyObject *key;
+ Py_hash_t hash;
+ int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys);
+ uint8_t new_size = Py_MAX(
+ estimate_log2_keysize(PyDict_GET_SIZE(iterable)),
+ DK_LOG_SIZE(mp->ma_keys));
+ if (dictresize(interp, mp, new_size, unicode)) {
+ Py_DECREF(mp);
+ return NULL;
+ }
+
+ while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) {
+ if (insertdict(interp, mp,
+ Py_NewRef(key), hash, Py_NewRef(value))) {
+ Py_DECREF(mp);
+ return NULL;
+ }
+ }
+ return mp;
+}
+
+static PyDictObject *
+dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
+ PyObject *iterable, PyObject *value)
+{
+ Py_ssize_t pos = 0;
+ PyObject *key;
+ Py_hash_t hash;
+
+ if (dictresize(interp, mp,
+ estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) {
+ Py_DECREF(mp);
+ return NULL;
+ }
+
+ while (_PySet_NextEntry(iterable, &pos, &key, &hash)) {
+ if (insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value))) {
+ Py_DECREF(mp);
+ return NULL;
+ }
+ }
+ return mp;
+}
/* Internal version of dict.from_keys(). It is subclass-friendly. */
PyObject *
if (d == NULL)
return NULL;
- if (PyDict_CheckExact(d) && ((PyDictObject *)d)->ma_used == 0) {
+
+ if (PyDict_CheckExact(d)) {
if (PyDict_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;
- PyObject *oldvalue;
- Py_ssize_t pos = 0;
- PyObject *key;
- Py_hash_t hash;
-
- int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys);
- if (dictresize(interp, mp,
- estimate_log2_keysize(PyDict_GET_SIZE(iterable)),
- unicode)) {
- Py_DECREF(d);
- return NULL;
- }
- while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) {
- if (insertdict(interp, mp,
- Py_NewRef(key), hash, Py_NewRef(value))) {
- Py_DECREF(d);
- return NULL;
- }
- }
+ Py_BEGIN_CRITICAL_SECTION2(d, iterable);
+ d = (PyObject *)dict_dict_fromkeys(interp, mp, iterable, value);
+ Py_END_CRITICAL_SECTION2();
return d;
}
- if (PyAnySet_CheckExact(iterable)) {
+ else if (PyAnySet_CheckExact(iterable)) {
PyDictObject *mp = (PyDictObject *)d;
- Py_ssize_t pos = 0;
- PyObject *key;
- Py_hash_t hash;
-
- if (dictresize(interp, mp,
- estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) {
- Py_DECREF(d);
- return NULL;
- }
- while (_PySet_NextEntry(iterable, &pos, &key, &hash)) {
- if (insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value))) {
- Py_DECREF(d);
- return NULL;
- }
- }
+ Py_BEGIN_CRITICAL_SECTION2(d, iterable);
+ d = (PyObject *)dict_set_fromkeys(interp, mp, iterable, value);
+ Py_END_CRITICAL_SECTION2();
return d;
}
}
}
if (PyDict_CheckExact(d)) {
+ Py_BEGIN_CRITICAL_SECTION(d);
while ((key = PyIter_Next(it)) != NULL) {
- status = PyDict_SetItem(d, key, value);
+ status = setitem_lock_held((PyDictObject *)d, key, value);
Py_DECREF(key);
- if (status < 0)
- goto Fail;
+ if (status < 0) {
+ assert(PyErr_Occurred());
+ goto dict_iter_exit;
+ }
}
+dict_iter_exit:
+ Py_END_CRITICAL_SECTION();
} else {
while ((key = PyIter_Next(it)) != NULL) {
status = PyObject_SetItem(d, key, value);
static PyObject *
-dict_repr(PyObject *self)
+dict_repr_lock_held(PyObject *self)
{
PyDictObject *mp = (PyDictObject *)self;
Py_ssize_t i;
Note that repr may mutate the dict. */
i = 0;
first = 1;
- while (PyDict_Next((PyObject *)mp, &i, &key, &value)) {
+ while (_PyDict_Next((PyObject *)mp, &i, &key, &value, NULL)) {
PyObject *s;
int res;
return NULL;
}
+static PyObject *
+dict_repr(PyObject *self)
+{
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ res = dict_repr_lock_held(self);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
static Py_ssize_t
dict_length(PyObject *self)
{
PyDictObject *mp = (PyDictObject *)self;
- return mp->ma_used;
+ return _Py_atomic_load_ssize_relaxed(&mp->ma_used);
}
static PyObject *
-dict_subscript(PyObject *self, PyObject *key)
+dict_subscript_lock_held(PyObject *self, PyObject *key)
{
PyDictObject *mp = (PyDictObject *)self;
Py_ssize_t ix;
return Py_NewRef(value);
}
+static PyObject *
+dict_subscript(PyObject *self, PyObject *key)
+{
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION(self);
+ res = dict_subscript_lock_held(self, key);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
static int
dict_ass_sub(PyObject *mp, PyObject *v, PyObject *w)
{
dict_ass_sub, /*mp_ass_subscript*/
};
-PyObject *
-PyDict_Keys(PyObject *dict)
+static PyObject *
+keys_lock_held(PyObject *dict)
{
+ ASSERT_DICT_LOCKED(dict);
+
if (dict == NULL || !PyDict_Check(dict)) {
PyErr_BadInternalCall();
return NULL;
}
PyObject *
-PyDict_Values(PyObject *dict)
+PyDict_Keys(PyObject *dict)
+{
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION(dict);
+ res = keys_lock_held(dict);
+ Py_END_CRITICAL_SECTION();
+
+ return res;
+}
+
+static PyObject *
+values_lock_held(PyObject *dict)
{
+ ASSERT_DICT_LOCKED(dict);
+
if (dict == NULL || !PyDict_Check(dict)) {
PyErr_BadInternalCall();
return NULL;
}
PyObject *
-PyDict_Items(PyObject *dict)
+PyDict_Values(PyObject *dict)
+{
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION(dict);
+ res = values_lock_held(dict);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
+static PyObject *
+items_lock_held(PyObject *dict)
{
+ ASSERT_DICT_LOCKED(dict);
+
if (dict == NULL || !PyDict_Check(dict)) {
PyErr_BadInternalCall();
return NULL;
return v;
}
+PyObject *
+PyDict_Items(PyObject *dict)
+{
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION(dict);
+ res = items_lock_held(dict);
+ Py_END_CRITICAL_SECTION();
+
+ return res;
+}
+
/*[clinic input]
@classmethod
dict.fromkeys
producing iterable objects of length 2.
*/
-int
-PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
+static int
+merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override)
{
PyObject *it; /* iter(seq2) */
Py_ssize_t i; /* index into seq2 of current element */
Py_INCREF(key);
Py_INCREF(value);
if (override) {
- if (PyDict_SetItem(d, key, value) < 0) {
+ if (setitem_lock_held((PyDictObject *)d, key, value) < 0) {
Py_DECREF(key);
Py_DECREF(value);
goto Fail;
}
}
else {
- if (PyDict_SetDefault(d, key, value) == NULL) {
+ if (dict_setdefault_ref_lock_held(d, key, value, NULL, 0) < 0) {
Py_DECREF(key);
Py_DECREF(value);
goto Fail;
return Py_SAFE_DOWNCAST(i, Py_ssize_t, int);
}
+int
+PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(d);
+ res = merge_from_seq2_lock_held(d, seq2, override);
+ Py_END_CRITICAL_SECTION();
+
+ return res;
+}
+
+static int
+dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *other, int override)
+{
+ if (other == mp || other->ma_used == 0)
+ /* a.update(a) or a.update({}); nothing to do */
+ return 0;
+ if (mp->ma_used == 0) {
+ /* Since the target dict is empty, PyDict_GetItem()
+ * always returns NULL. Setting override to 1
+ * skips the unnecessary test.
+ */
+ override = 1;
+ PyDictKeysObject *okeys = other->ma_keys;
+
+ // If other is clean, combined, and just allocated, just clone it.
+ if (other->ma_values == NULL &&
+ other->ma_used == okeys->dk_nentries &&
+ (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE ||
+ USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)) {
+ uint64_t new_version = _PyDict_NotifyEvent(
+ interp, PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL);
+ PyDictKeysObject *keys = clone_combined_dict_keys(other);
+ if (keys == NULL)
+ return -1;
+
+ dictkeys_decref(interp, mp->ma_keys);
+ mp->ma_keys = keys;
+ if (mp->ma_values != NULL) {
+ free_values(mp->ma_values);
+ mp->ma_values = NULL;
+ }
+
+ mp->ma_used = other->ma_used;
+ mp->ma_version_tag = new_version;
+ ASSERT_CONSISTENT(mp);
+
+ if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) {
+ /* Maintain tracking. */
+ _PyObject_GC_TRACK(mp);
+ }
+
+ return 0;
+ }
+ }
+ /* Do one big resize at the start, rather than
+ * incrementally resizing as we insert new items. Expect
+ * that there will be no (or few) overlapping keys.
+ */
+ if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) {
+ int unicode = DK_IS_UNICODE(other->ma_keys);
+ if (dictresize(interp, mp,
+ estimate_log2_keysize(mp->ma_used + other->ma_used),
+ unicode)) {
+ return -1;
+ }
+ }
+
+ Py_ssize_t orig_size = other->ma_keys->dk_nentries;
+ Py_ssize_t pos = 0;
+ Py_hash_t hash;
+ PyObject *key, *value;
+
+ while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) {
+ int err = 0;
+ Py_INCREF(key);
+ Py_INCREF(value);
+ if (override == 1) {
+ err = insertdict(interp, mp,
+ Py_NewRef(key), hash, Py_NewRef(value));
+ }
+ else {
+ err = contains_known_hash_lock_held(mp, key, hash);
+ if (err == 0) {
+ err = insertdict(interp, mp,
+ Py_NewRef(key), hash, Py_NewRef(value));
+ }
+ else if (err > 0) {
+ if (override != 0) {
+ _PyErr_SetKeyError(key);
+ Py_DECREF(value);
+ Py_DECREF(key);
+ return -1;
+ }
+ err = 0;
+ }
+ }
+ Py_DECREF(value);
+ Py_DECREF(key);
+ if (err != 0)
+ return -1;
+
+ if (orig_size != other->ma_keys->dk_nentries) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "dict mutated during update");
+ return -1;
+ }
+ }
+ return 0;
+}
+
static int
dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override)
{
return -1;
}
mp = (PyDictObject*)a;
+ int res = 0;
if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
other = (PyDictObject*)b;
- if (other == mp || other->ma_used == 0)
- /* a.update(a) or a.update({}); nothing to do */
- return 0;
- if (mp->ma_used == 0) {
- /* Since the target dict is empty, PyDict_GetItem()
- * always returns NULL. Setting override to 1
- * skips the unnecessary test.
- */
- override = 1;
- PyDictKeysObject *okeys = other->ma_keys;
-
- // If other is clean, combined, and just allocated, just clone it.
- if (other->ma_values == NULL &&
- other->ma_used == okeys->dk_nentries &&
- (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE ||
- USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)) {
- uint64_t new_version = _PyDict_NotifyEvent(
- interp, PyDict_EVENT_CLONED, mp, b, NULL);
- PyDictKeysObject *keys = clone_combined_dict_keys(other);
- if (keys == NULL) {
- return -1;
- }
-
- dictkeys_decref(interp, mp->ma_keys);
- mp->ma_keys = keys;
- if (mp->ma_values != NULL) {
- free_values(mp->ma_values);
- mp->ma_values = NULL;
- }
-
- mp->ma_used = other->ma_used;
- mp->ma_version_tag = new_version;
- ASSERT_CONSISTENT(mp);
-
- if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) {
- /* Maintain tracking. */
- _PyObject_GC_TRACK(mp);
- }
-
- return 0;
- }
- }
- /* Do one big resize at the start, rather than
- * incrementally resizing as we insert new items. Expect
- * that there will be no (or few) overlapping keys.
- */
- if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) {
- int unicode = DK_IS_UNICODE(other->ma_keys);
- if (dictresize(interp, mp,
- estimate_log2_keysize(mp->ma_used + other->ma_used),
- unicode)) {
- return -1;
- }
- }
-
- Py_ssize_t orig_size = other->ma_keys->dk_nentries;
- Py_ssize_t pos = 0;
- Py_hash_t hash;
- PyObject *key, *value;
-
- while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) {
- int err = 0;
- Py_INCREF(key);
- Py_INCREF(value);
- if (override == 1) {
- err = insertdict(interp, mp,
- Py_NewRef(key), hash, Py_NewRef(value));
- }
- else {
- err = _PyDict_Contains_KnownHash(a, key, hash);
- if (err == 0) {
- err = insertdict(interp, mp,
- Py_NewRef(key), hash, Py_NewRef(value));
- }
- else if (err > 0) {
- if (override != 0) {
- _PyErr_SetKeyError(key);
- Py_DECREF(value);
- Py_DECREF(key);
- return -1;
- }
- err = 0;
- }
- }
- Py_DECREF(value);
- Py_DECREF(key);
- if (err != 0)
- return -1;
-
- if (orig_size != other->ma_keys->dk_nentries) {
- PyErr_SetString(PyExc_RuntimeError,
- "dict mutated during update");
- return -1;
- }
- }
+ int res;
+ Py_BEGIN_CRITICAL_SECTION2(a, b);
+ res = dict_dict_merge(interp, (PyDictObject *)a, other, override);
+ ASSERT_CONSISTENT(a);
+ Py_END_CRITICAL_SECTION2();
+ return res;
}
else {
/* Do it the generic, slower way */
+ Py_BEGIN_CRITICAL_SECTION(a);
PyObject *keys = PyMapping_Keys(b);
PyObject *iter;
PyObject *key, *value;
int status;
- if (keys == NULL)
+ if (keys == NULL) {
/* Docstring says this is equivalent to E.keys() so
* if E doesn't have a .keys() method we want
* AttributeError to percolate up. Might as well
* do the same for any other error.
*/
- return -1;
+ res = -1;
+ goto slow_exit;
+ }
iter = PyObject_GetIter(keys);
Py_DECREF(keys);
- if (iter == NULL)
- return -1;
+ if (iter == NULL) {
+ res = -1;
+ goto slow_exit;
+ }
for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) {
if (override != 1) {
- status = PyDict_Contains(a, key);
+ status = contains_lock_held(mp, key);
if (status != 0) {
if (status > 0) {
if (override == 0) {
}
Py_DECREF(key);
Py_DECREF(iter);
- return -1;
+ res = -1;
+ goto slow_exit;
}
}
value = PyObject_GetItem(b, key);
if (value == NULL) {
Py_DECREF(iter);
Py_DECREF(key);
- return -1;
+ res = -1;
+ goto slow_exit;
}
- status = PyDict_SetItem(a, key, value);
+ status = setitem_lock_held(mp, key, value);
Py_DECREF(key);
Py_DECREF(value);
if (status < 0) {
Py_DECREF(iter);
+ res = -1;
+ goto slow_exit;
return -1;
}
}
Py_DECREF(iter);
- if (PyErr_Occurred())
+ if (PyErr_Occurred()) {
/* Iterator completed, via error */
- return -1;
+ res = -1;
+ goto slow_exit;
+ }
+
+slow_exit:
+ ASSERT_CONSISTENT(a);
+ Py_END_CRITICAL_SECTION();
+ return res;
}
- ASSERT_CONSISTENT(a);
- return 0;
}
int
return PyDict_Copy((PyObject *)self);
}
-PyObject *
-PyDict_Copy(PyObject *o)
+static PyObject *
+copy_lock_held(PyObject *o)
{
PyObject *copy;
PyDictObject *mp;
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (o == NULL || !PyDict_Check(o)) {
- PyErr_BadInternalCall();
- return NULL;
- }
+ ASSERT_DICT_LOCKED(o);
mp = (PyDictObject *)o;
if (mp->ma_used == 0) {
return NULL;
}
+PyObject *
+PyDict_Copy(PyObject *o)
+{
+ if (o == NULL || !PyDict_Check(o)) {
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION(o);
+
+ res = copy_lock_held(o);
+
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
Py_ssize_t
PyDict_Size(PyObject *mp)
{
* Uses only Py_EQ comparison.
*/
static int
-dict_equal(PyDictObject *a, PyDictObject *b)
+dict_equal_lock_held(PyDictObject *a, PyDictObject *b)
{
Py_ssize_t i;
+ ASSERT_DICT_LOCKED(a);
+ ASSERT_DICT_LOCKED(b);
+
if (a->ma_used != b->ma_used)
/* can't be equal if # of entries differ */
return 0;
return 1;
}
+static int
+dict_equal(PyDictObject *a, PyDictObject *b)
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION2(a, b);
+ res = dict_equal_lock_held(a, b);
+ Py_END_CRITICAL_SECTION2();
+
+ return res;
+}
+
static PyObject *
dict_richcompare(PyObject *v, PyObject *w, int op)
{
/*[clinic input]
@coexist
+@critical_section
dict.__contains__
key: object
[clinic start generated code]*/
static PyObject *
-dict___contains__(PyDictObject *self, PyObject *key)
-/*[clinic end generated code: output=a3d03db709ed6e6b input=fe1cb42ad831e820]*/
+dict___contains___impl(PyDictObject *self, PyObject *key)
+/*[clinic end generated code: output=1b314e6da7687dae input=bc76ec9c157cb81b]*/
{
register PyDictObject *mp = self;
Py_hash_t hash;
}
/*[clinic input]
+@critical_section
dict.get
key: object
static PyObject *
dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
-/*[clinic end generated code: output=bba707729dee05bf input=279ddb5790b6b107]*/
+/*[clinic end generated code: output=bba707729dee05bf input=a631d3f18f584c60]*/
{
PyObject *val = NULL;
Py_hash_t hash;
}
static int
-dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value,
+dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value,
PyObject **result, int incref_result)
{
PyDictObject *mp = (PyDictObject *)d;
Py_hash_t hash;
PyInterpreterState *interp = _PyInterpreterState_GET();
+ ASSERT_DICT_LOCKED(d);
+
if (!PyDict_Check(d)) {
PyErr_BadInternalCall();
if (result) {
PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value,
PyObject **result)
{
- return dict_setdefault_ref(d, key, default_value, result, 1);
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(d);
+ res = dict_setdefault_ref_lock_held(d, key, default_value, result, 1);
+ Py_END_CRITICAL_SECTION();
+ return res;
}
PyObject *
PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
{
PyObject *result;
- dict_setdefault_ref(d, key, defaultobj, &result, 0);
+ Py_BEGIN_CRITICAL_SECTION(d);
+ dict_setdefault_ref_lock_held(d, key, defaultobj, &result, 0);
+ Py_END_CRITICAL_SECTION();
return result;
}
/*[clinic input]
+@critical_section
dict.setdefault
key: object
static PyObject *
dict_setdefault_impl(PyDictObject *self, PyObject *key,
PyObject *default_value)
-/*[clinic end generated code: output=f8c1101ebf69e220 input=0f063756e815fd9d]*/
+/*[clinic end generated code: output=f8c1101ebf69e220 input=9237af9a0a224302]*/
{
PyObject *val;
- PyDict_SetDefaultRef((PyObject *)self, key, default_value, &val);
+ dict_setdefault_ref_lock_held((PyObject *)self, key, default_value, &val, 1);
return val;
}
}
/*[clinic input]
+@critical_section
dict.popitem
Remove and return a (key, value) pair as a 2-tuple.
static PyObject *
dict_popitem_impl(PyDictObject *self)
-/*[clinic end generated code: output=e65fcb04420d230d input=1c38a49f21f64941]*/
+/*[clinic end generated code: output=e65fcb04420d230d input=ef28b4da5f0f762e]*/
{
Py_ssize_t i, j;
PyObject *res;
static PyObject *dictiter_new(PyDictObject *, PyTypeObject *);
-Py_ssize_t
-_PyDict_SizeOf(PyDictObject *mp)
+static Py_ssize_t
+sizeof_lock_held(PyDictObject *mp)
{
size_t res = _PyObject_SIZE(Py_TYPE(mp));
if (mp->ma_values) {
return (Py_ssize_t)res;
}
+Py_ssize_t
+_PyDict_SizeOf(PyDictObject *mp)
+{
+ Py_ssize_t res;
+ Py_BEGIN_CRITICAL_SECTION(mp);
+ res = sizeof_lock_held(mp);
+ Py_END_CRITICAL_SECTION();
+
+ return res;
+}
+
size_t
_PyDict_KeysSize(PyDictKeysObject *keys)
{
{NULL, NULL} /* sentinel */
};
-/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */
-int
-PyDict_Contains(PyObject *op, PyObject *key)
+static int
+contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash)
+{
+ Py_ssize_t ix;
+ PyObject *value;
+
+ ASSERT_DICT_LOCKED(mp);
+
+ ix = _Py_dict_lookup(mp, key, hash, &value);
+ if (ix == DKIX_ERROR)
+ return -1;
+ return (ix != DKIX_EMPTY && value != NULL);
+}
+
+static int
+contains_lock_held(PyDictObject *mp, PyObject *key)
{
Py_hash_t hash;
Py_ssize_t ix;
- PyDictObject *mp = (PyDictObject *)op;
PyObject *value;
+ ASSERT_DICT_LOCKED(mp);
+
if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) {
hash = PyObject_Hash(key);
if (hash == -1)
return (ix != DKIX_EMPTY && value != NULL);
}
+/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */
+int
+PyDict_Contains(PyObject *op, PyObject *key)
+{
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ res = contains_lock_held((PyDictObject *)op, key);
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
int
PyDict_ContainsString(PyObject *op, const char *key)
{
};
static PyObject*
-dictiter_iternextkey(PyObject *self)
+dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self)
{
dictiterobject *di = (dictiterobject *)self;
PyObject *key;
Py_ssize_t i;
PyDictKeysObject *k;
- PyDictObject *d = di->di_dict;
- if (d == NULL)
- return NULL;
assert (PyDict_Check(d));
+ ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
PyErr_SetString(PyExc_RuntimeError,
return NULL;
}
+static PyObject*
+dictiter_iternextkey(PyObject *self)
+{
+ dictiterobject *di = (dictiterobject *)self;
+ PyDictObject *d = di->di_dict;
+
+ if (d == NULL)
+ return NULL;
+
+ PyObject *value;
+ Py_BEGIN_CRITICAL_SECTION(d);
+ value = dictiter_iternextkey_lock_held(d, self);
+ Py_END_CRITICAL_SECTION();
+
+ return value;
+}
+
PyTypeObject PyDictIterKey_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"dict_keyiterator", /* tp_name */
};
static PyObject *
-dictiter_iternextvalue(PyObject *self)
+dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self)
{
dictiterobject *di = (dictiterobject *)self;
PyObject *value;
Py_ssize_t i;
- PyDictObject *d = di->di_dict;
- if (d == NULL)
- return NULL;
assert (PyDict_Check(d));
+ ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
PyErr_SetString(PyExc_RuntimeError,
return NULL;
}
+static PyObject *
+dictiter_iternextvalue(PyObject *self)
+{
+ dictiterobject *di = (dictiterobject *)self;
+ PyDictObject *d = di->di_dict;
+
+ if (d == NULL)
+ return NULL;
+
+ PyObject *value;
+ Py_BEGIN_CRITICAL_SECTION(d);
+ value = dictiter_iternextvalue_lock_held(d, self);
+ Py_END_CRITICAL_SECTION();
+
+ return value;
+}
+
PyTypeObject PyDictIterValue_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"dict_valueiterator", /* tp_name */
};
static PyObject *
-dictiter_iternextitem(PyObject *self)
+dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self)
{
dictiterobject *di = (dictiterobject *)self;
PyObject *key, *value, *result;
Py_ssize_t i;
- PyDictObject *d = di->di_dict;
- if (d == NULL)
- return NULL;
assert (PyDict_Check(d));
if (di->di_used != d->ma_used) {
return NULL;
}
+static PyObject *
+dictiter_iternextitem(PyObject *self)
+{
+ dictiterobject *di = (dictiterobject *)self;
+ PyDictObject *d = di->di_dict;
+
+ if (d == NULL)
+ return NULL;
+
+ PyObject *item;
+ Py_BEGIN_CRITICAL_SECTION(d);
+ item = dictiter_iternextitem_lock_held(d, self);
+ Py_END_CRITICAL_SECTION();
+ return item;
+}
+
PyTypeObject PyDictIterItem_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"dict_itemiterator", /* tp_name */
/* dictreviter */
static PyObject *
-dictreviter_iternext(PyObject *self)
+dictreviter_iter_PyDict_Next(PyDictObject *d, PyObject *self)
{
dictiterobject *di = (dictiterobject *)self;
- PyDictObject *d = di->di_dict;
- if (d == NULL) {
- return NULL;
- }
assert (PyDict_Check(d));
+ ASSERT_DICT_LOCKED(d);
if (di->di_used != d->ma_used) {
PyErr_SetString(PyExc_RuntimeError,
return NULL;
}
+static PyObject *
+dictreviter_iternext(PyObject *self)
+{
+ dictiterobject *di = (dictiterobject *)self;
+ PyDictObject *d = di->di_dict;
+
+ if (d == NULL)
+ return NULL;
+
+ PyObject *value;
+ Py_BEGIN_CRITICAL_SECTION(d);
+ value = dictreviter_iter_PyDict_Next(d, self);
+ Py_END_CRITICAL_SECTION();
+
+ return value;
+}
+
PyTypeObject PyDictRevIterKey_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"dict_reversekeyiterator",
}
static PyObject *
-dictitems_xor(PyObject *self, PyObject *other)
+dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
{
- assert(PyDictItems_Check(self));
- assert(PyDictItems_Check(other));
- PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict;
- PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict;
+ ASSERT_DICT_LOCKED(d1);
+ ASSERT_DICT_LOCKED(d2);
- PyObject *temp_dict = PyDict_Copy(d1);
+ PyObject *temp_dict = copy_lock_held(d1);
if (temp_dict == NULL) {
return NULL;
}
return NULL;
}
+static PyObject *
+dictitems_xor(PyObject *self, PyObject *other)
+{
+ assert(PyDictItems_Check(self));
+ assert(PyDictItems_Check(other));
+ PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict;
+ PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict;
+
+ PyObject *res;
+ Py_BEGIN_CRITICAL_SECTION2(d1, d2);
+ res = dictitems_xor_lock_held(d1, d2);
+ Py_END_CRITICAL_SECTION2();
+
+ return res;
+}
+
static PyObject*
dictviews_xor(PyObject* self, PyObject *other)
{