Skip to content

Commit 699f4e9

Browse files
committed
Add locking for split dict keys.
For `specialize_dict_access_inline()`, we need to lock the keys object. * Add `_PyDictKeys_StringLookupSplit` which does required locking and use in place of `_PyDictKeys_StringLookup`. * Change `_PyObject_TryGetInstanceAttribute` to use that function in the case of split keys. * Add `unicodekeys_lookup_split` helper which allows code sharing between `_Py_dict_lookup` and `_PyDictKeys_StringLookupSplit`.
1 parent 94294e9 commit 699f4e9

File tree

3 files changed

+59
-18
lines changed

3 files changed

+59
-18
lines changed

Include/internal/pycore_dict.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ extern Py_ssize_t _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject
114114

115115
extern Py_ssize_t _PyDict_LookupIndex(PyDictObject *, PyObject *);
116116
extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject *key);
117+
extern Py_ssize_t _PyDictKeys_StringLookupSplit(PyDictKeysObject* dictkeys, PyObject *key);
117118
PyAPI_FUNC(PyObject *)_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *);
118119
PyAPI_FUNC(void) _PyDict_LoadGlobalStackRef(PyDictObject *, PyDictObject *, PyObject *, _PyStackRef *);
119120

Objects/dictobject.c

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,38 @@ dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, P
11291129
return do_lookup(mp, dk, key, hash, compare_generic);
11301130
}
11311131

1132+
#ifdef Py_GIL_DISABLED
1133+
static Py_ssize_t
1134+
unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key,
1135+
Py_hash_t hash);
1136+
#endif
1137+
1138+
static Py_ssize_t
1139+
unicodekeys_lookup_split(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
1140+
{
1141+
Py_ssize_t ix;
1142+
assert(dk->dk_kind == DICT_KEYS_SPLIT);
1143+
assert(PyUnicode_CheckExact(key));
1144+
1145+
#ifdef Py_GIL_DISABLED
1146+
// A split dictionaries keys can be mutated by other dictionaries
1147+
// but if we have a unicode key we can avoid locking the shared
1148+
// keys.
1149+
ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1150+
if (ix == DKIX_KEY_CHANGED) {
1151+
LOCK_KEYS(dk);
1152+
ix = unicodekeys_lookup_unicode(dk, key, hash);
1153+
UNLOCK_KEYS(dk);
1154+
}
1155+
else {
1156+
ix = unicodekeys_lookup_unicode(dk, key, hash);
1157+
}
1158+
#else
1159+
ix = unicodekeys_lookup_unicode(dk, key, hash);
1160+
#endif
1161+
return ix;
1162+
}
1163+
11321164
/* Lookup a string in a (all unicode) dict keys.
11331165
* Returns DKIX_ERROR if key is not a string,
11341166
* or if the dict keys is not all strings.
@@ -1153,13 +1185,24 @@ _PyDictKeys_StringLookup(PyDictKeysObject* dk, PyObject *key)
11531185
return unicodekeys_lookup_unicode(dk, key, hash);
11541186
}
11551187

1156-
#ifdef Py_GIL_DISABLED
1157-
1158-
static Py_ssize_t
1159-
unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key,
1160-
Py_hash_t hash);
1161-
1162-
#endif
1188+
/* Like _PyDictKeys_StringLookup() but only works on split keys. Note
1189+
* that in free-threaded builds this locks the keys object as required.
1190+
*/
1191+
Py_ssize_t
1192+
_PyDictKeys_StringLookupSplit(PyDictKeysObject* dk, PyObject *key)
1193+
{
1194+
assert(dk->dk_kind == DICT_KEYS_SPLIT);
1195+
assert(PyUnicode_CheckExact(key));
1196+
Py_hash_t hash = unicode_get_hash(key);
1197+
if (hash == -1) {
1198+
hash = PyUnicode_Type.tp_hash(key);
1199+
if (hash == -1) {
1200+
PyErr_Clear();
1201+
return DKIX_ERROR;
1202+
}
1203+
}
1204+
return unicodekeys_lookup_split(dk, key, hash);
1205+
}
11631206

11641207
/*
11651208
The basic lookup function used by all operations.
@@ -1192,15 +1235,7 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu
11921235
if (PyUnicode_CheckExact(key)) {
11931236
#ifdef Py_GIL_DISABLED
11941237
if (kind == DICT_KEYS_SPLIT) {
1195-
// A split dictionaries keys can be mutated by other
1196-
// dictionaries but if we have a unicode key we can avoid
1197-
// locking the shared keys.
1198-
ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1199-
if (ix == DKIX_KEY_CHANGED) {
1200-
LOCK_KEYS(dk);
1201-
ix = unicodekeys_lookup_unicode(dk, key, hash);
1202-
UNLOCK_KEYS(dk);
1203-
}
1238+
ix = unicodekeys_lookup_split(dk, key, hash);
12041239
}
12051240
else {
12061241
ix = unicodekeys_lookup_unicode(dk, key, hash);
@@ -6967,7 +7002,12 @@ _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr
69677002

69687003
PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
69697004
assert(keys != NULL);
6970-
Py_ssize_t ix = _PyDictKeys_StringLookup(keys, name);
7005+
Py_ssize_t ix;
7006+
if (keys->dk_kind == DICT_KEYS_SPLIT) {
7007+
ix = _PyDictKeys_StringLookupSplit(keys, name);
7008+
} else {
7009+
ix = _PyDictKeys_StringLookup(keys, name);
7010+
}
69717011
if (ix == DKIX_EMPTY) {
69727012
*attr = NULL;
69737013
return true;

Python/specialize.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ specialize_dict_access_inline(
967967
_PyAttrCache *cache = (_PyAttrCache *)(instr + 1);
968968
PyDictKeysObject *keys = ((PyHeapTypeObject *)type)->ht_cached_keys;
969969
assert(PyUnicode_CheckExact(name));
970-
Py_ssize_t index = _PyDictKeys_StringLookup(keys, name);
970+
Py_ssize_t index = _PyDictKeys_StringLookupSplit(keys, name);
971971
assert (index != DKIX_ERROR);
972972
if (index == DKIX_EMPTY) {
973973
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_NOT_IN_KEYS);

0 commit comments

Comments
 (0)