Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-112075: Make _PyDict_LoadGlobal thread safe #117529

Merged
merged 2 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ extern void _PyDictKeys_DecRef(PyDictKeysObject *keys);
* -1 when no entry found, -3 when compare raises error.
*/
extern Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
extern Py_ssize_t _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);

extern Py_ssize_t _PyDict_LookupIndex(PyDictObject *, PyObject *);
extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject *key);
Expand Down
27 changes: 21 additions & 6 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,6 @@ compare_unicode_generic(PyDictObject *mp, PyDictKeysObject *dk,
assert(ep->me_key != NULL);
assert(PyUnicode_CheckExact(ep->me_key));
assert(!PyUnicode_CheckExact(key));
// TODO: Thread safety

if (unicode_get_hash(ep->me_key) == hash) {
PyObject *startkey = ep->me_key;
Expand Down Expand Up @@ -1192,7 +1191,8 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu
PyDictKeysObject *dk;
DictKeysKind kind;
Py_ssize_t ix;
// TODO: Thread safety

_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp);
start:
dk = mp->ma_keys;
kind = dk->dk_kind;
Expand Down Expand Up @@ -1390,7 +1390,7 @@ dictkeys_generic_lookup_threadsafe(PyDictObject *mp, PyDictKeysObject* dk, PyObj
return do_lookup(mp, dk, key, hash, compare_generic_threadsafe);
}

static Py_ssize_t
Py_ssize_t
_Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr)
{
PyDictKeysObject *dk;
Expand Down Expand Up @@ -2343,11 +2343,12 @@ _PyDict_GetItemStringWithError(PyObject *v, const char *key)
* Raise an exception and return NULL if an error occurred (ex: computing the
* key hash failed, key comparison failed, ...). Return NULL if the key doesn't
* exist. Return the value if the key exists.
*
* Returns a new reference.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will want it to return a possibly deferred reference soon, but this is good for now.

*/
PyObject *
_PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key)
{
// TODO: Thread safety
Py_ssize_t ix;
Py_hash_t hash;
PyObject *value;
Expand All @@ -2358,17 +2359,31 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key)
return NULL;
}

#ifdef Py_GIL_DISABLED
/* namespace 1: globals */
ix = _Py_dict_lookup(globals, key, hash, &value);
ix = _Py_dict_lookup_threadsafe(globals, key, hash, &value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this pattern might be simpler if we define _Py_dict_lookup_threadsafe in the default build as _Py_dict_lookup_threadsafe + Py_XNewRef().

We could use it in dict_subscript and dict_get_impl as well.

if (ix == DKIX_ERROR)
return NULL;
if (ix != DKIX_EMPTY && value != NULL)
return value;

/* namespace 2: builtins */
ix = _Py_dict_lookup(builtins, key, hash, &value);
ix = _Py_dict_lookup_threadsafe(builtins, key, hash, &value);
assert(ix >= 0 || value == NULL);
return value;
#else
/* namespace 1: globals */
ix = _Py_dict_lookup(globals, key, hash, &value);
if (ix == DKIX_ERROR)
return NULL;
if (ix != DKIX_EMPTY && value != NULL)
return Py_NewRef(value);

/* namespace 2: builtins */
ix = _Py_dict_lookup(builtins, key, hash, &value);
assert(ix >= 0 || value == NULL);
return Py_XNewRef(value);
#endif
}

/* Consumes references to key and value */
Expand Down
6 changes: 5 additions & 1 deletion Objects/odictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,12 @@ _odict_get_index_raw(PyODictObject *od, PyObject *key, Py_hash_t hash)
PyObject *value = NULL;
PyDictKeysObject *keys = ((PyDictObject *)od)->ma_keys;
Py_ssize_t ix;

#ifdef Py_GIL_DISABLED
ix = _Py_dict_lookup_threadsafe((PyDictObject *)od, key, hash, &value);
Py_XDECREF(value);
#else
ix = _Py_dict_lookup((PyDictObject *)od, key, hash, &value);
#endif
if (ix == DKIX_EMPTY) {
return keys->dk_nentries; /* index of new entry */
}
Expand Down
1 change: 0 additions & 1 deletion Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,6 @@ dummy_func(
}
ERROR_IF(true, error);
}
Py_INCREF(res);
}
else {
/* Slow-path if globals or builtins is not a dict */
Expand Down
1 change: 0 additions & 1 deletion Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading