-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-112075: Make some dict
operations thread-safe without GIL
#112247
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -117,6 +117,7 @@ As a consequence of this, split keys have a maximum size of 16. | |
#include "pycore_call.h" // _PyObject_CallNoArgs() | ||
#include "pycore_ceval.h" // _PyEval_GetBuiltin() | ||
#include "pycore_code.h" // stats | ||
#include "pycore_critical_section.h" | ||
#include "pycore_dict.h" // export _PyDict_SizeOf() | ||
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() | ||
#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() | ||
|
@@ -2505,15 +2506,18 @@ dict_repr(PyDictObject *mp) | |
PyObject *key = NULL, *value = NULL; | ||
_PyUnicodeWriter writer; | ||
int first; | ||
PyObject *repr = NULL; | ||
|
||
i = Py_ReprEnter((PyObject *)mp); | ||
if (i != 0) { | ||
return i > 0 ? PyUnicode_FromString("{...}") : NULL; | ||
} | ||
|
||
Py_BEGIN_CRITICAL_SECTION(mp); | ||
|
||
if (mp->ma_used == 0) { | ||
Py_ReprLeave((PyObject *)mp); | ||
return PyUnicode_FromString("{}"); | ||
repr = PyUnicode_FromString("{}"); | ||
goto end; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thin this may incorrectly call For these functions with complex control flow, I think it's better to add a wrapper function than try to work
|
||
} | ||
|
||
_PyUnicodeWriter_Init(&writer); | ||
|
@@ -2522,7 +2526,7 @@ dict_repr(PyDictObject *mp) | |
writer.min_length = 1 + 4 + (2 + 4) * (mp->ma_used - 1) + 1; | ||
|
||
if (_PyUnicodeWriter_WriteChar(&writer, '{') < 0) | ||
goto error; | ||
goto end; | ||
|
||
/* Do repr() on each key+value pair, and insert ": " between them. | ||
Note that repr may mutate the dict. */ | ||
|
@@ -2538,53 +2542,58 @@ dict_repr(PyDictObject *mp) | |
|
||
if (!first) { | ||
if (_PyUnicodeWriter_WriteASCIIString(&writer, ", ", 2) < 0) | ||
goto error; | ||
goto end; | ||
} | ||
first = 0; | ||
|
||
s = PyObject_Repr(key); | ||
if (s == NULL) | ||
goto error; | ||
goto end; | ||
res = _PyUnicodeWriter_WriteStr(&writer, s); | ||
Py_DECREF(s); | ||
if (res < 0) | ||
goto error; | ||
goto end; | ||
|
||
if (_PyUnicodeWriter_WriteASCIIString(&writer, ": ", 2) < 0) | ||
goto error; | ||
goto end; | ||
|
||
s = PyObject_Repr(value); | ||
if (s == NULL) | ||
goto error; | ||
goto end; | ||
res = _PyUnicodeWriter_WriteStr(&writer, s); | ||
Py_DECREF(s); | ||
if (res < 0) | ||
goto error; | ||
goto end; | ||
|
||
Py_CLEAR(key); | ||
Py_CLEAR(value); | ||
} | ||
|
||
writer.overallocate = 0; | ||
if (_PyUnicodeWriter_WriteChar(&writer, '}') < 0) | ||
goto error; | ||
|
||
Py_ReprLeave((PyObject *)mp); | ||
goto end; | ||
|
||
return _PyUnicodeWriter_Finish(&writer); | ||
repr = _PyUnicodeWriter_Finish(&writer); | ||
|
||
error: | ||
end: | ||
Py_ReprLeave((PyObject *)mp); | ||
_PyUnicodeWriter_Dealloc(&writer); | ||
Py_XDECREF(key); | ||
Py_XDECREF(value); | ||
return NULL; | ||
if (repr == NULL) { | ||
_PyUnicodeWriter_Dealloc(&writer); | ||
Py_XDECREF(key); | ||
Py_XDECREF(value); | ||
} | ||
Py_END_CRITICAL_SECTION(); | ||
return repr; | ||
} | ||
|
||
static Py_ssize_t | ||
dict_length(PyDictObject *mp) | ||
{ | ||
#ifdef Py_NOGIL | ||
return _Py_atomic_load_ssize_relaxed(&mp->ma_used); | ||
#else | ||
return mp->ma_used; | ||
#endif | ||
} | ||
|
||
static PyObject * | ||
|
@@ -2746,6 +2755,7 @@ dict_items(PyDictObject *mp) | |
} | ||
|
||
/*[clinic input] | ||
@critical_section | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We may need critical sections for some code paths of |
||
@classmethod | ||
dict.fromkeys | ||
iterable: object | ||
|
@@ -2757,7 +2767,7 @@ Create a new dictionary with keys from iterable and values set to value. | |
|
||
static PyObject * | ||
dict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value) | ||
/*[clinic end generated code: output=8fb98e4b10384999 input=382ba4855d0f74c3]*/ | ||
/*[clinic end generated code: output=8fb98e4b10384999 input=8eef23b63d38c243]*/ | ||
{ | ||
return _PyDict_FromKeys((PyObject *)type, iterable, value); | ||
} | ||
|
@@ -2790,7 +2800,9 @@ dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, | |
result = -1; | ||
} | ||
else if (arg != NULL) { | ||
Py_BEGIN_CRITICAL_SECTION2(self, arg); | ||
result = dict_update_arg(self, arg); | ||
Py_END_CRITICAL_SECTION2(); | ||
} | ||
|
||
if (result == 0 && kwds != NULL) { | ||
|
@@ -3107,7 +3119,11 @@ _PyDict_MergeEx(PyObject *a, PyObject *b, int override) | |
static PyObject * | ||
dict_copy(PyDictObject *mp, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
return PyDict_Copy((PyObject*)mp); | ||
PyObject *copy; | ||
Py_BEGIN_CRITICAL_SECTION(mp); | ||
copy = PyDict_Copy((PyObject*)mp); | ||
Py_END_CRITICAL_SECTION(); | ||
return copy; | ||
} | ||
|
||
PyObject * | ||
|
@@ -3316,7 +3332,9 @@ dict_richcompare(PyObject *v, PyObject *w, int op) | |
res = Py_NotImplemented; | ||
} | ||
else if (op == Py_EQ || op == Py_NE) { | ||
Py_BEGIN_CRITICAL_SECTION2(v, w); | ||
cmp = dict_equal((PyDictObject *)v, (PyDictObject *)w); | ||
Py_END_CRITICAL_SECTION2(); | ||
if (cmp < 0) | ||
return NULL; | ||
res = (cmp == (op == Py_EQ)) ? Py_True : Py_False; | ||
|
@@ -3511,7 +3529,9 @@ dict_setdefault_impl(PyDictObject *self, PyObject *key, | |
static PyObject * | ||
dict_clear(PyDictObject *mp, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
Py_BEGIN_CRITICAL_SECTION(mp); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need public APIs like I suggest doing something like in colesbury/nogil-3.12@d896dfc8db:
|
||
PyDict_Clear((PyObject *)mp); | ||
Py_END_CRITICAL_SECTION(); | ||
Py_RETURN_NONE; | ||
} | ||
|
||
|
@@ -3702,7 +3722,11 @@ _PyDict_KeysSize(PyDictKeysObject *keys) | |
static PyObject * | ||
dict_sizeof(PyDictObject *mp, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
return PyLong_FromSsize_t(_PyDict_SizeOf(mp)); | ||
PyObject *size; | ||
Py_BEGIN_CRITICAL_SECTION(mp); | ||
size = PyLong_FromSsize_t(_PyDict_SizeOf(mp)); | ||
Py_END_CRITICAL_SECTION(); | ||
return size; | ||
} | ||
|
||
static PyObject * | ||
|
@@ -3711,24 +3735,31 @@ dict_or(PyObject *self, PyObject *other) | |
if (!PyDict_Check(self) || !PyDict_Check(other)) { | ||
Py_RETURN_NOTIMPLEMENTED; | ||
} | ||
PyObject *new = PyDict_Copy(self); | ||
|
||
PyObject *new = NULL; | ||
Py_BEGIN_CRITICAL_SECTION2(self, other); | ||
new = PyDict_Copy(self); | ||
if (new == NULL) { | ||
return NULL; | ||
goto end; | ||
} | ||
if (dict_update_arg(new, other)) { | ||
Py_DECREF(new); | ||
return NULL; | ||
Py_CLEAR(new); | ||
goto end; | ||
} | ||
|
||
end: | ||
Py_END_CRITICAL_SECTION2(); | ||
return new; | ||
} | ||
|
||
static PyObject * | ||
dict_ior(PyObject *self, PyObject *other) | ||
{ | ||
if (dict_update_arg(self, other)) { | ||
return NULL; | ||
} | ||
return Py_NewRef(self); | ||
int r; | ||
Py_BEGIN_CRITICAL_SECTION2(self, other); | ||
r = dict_update_arg(self, other); | ||
Py_END_CRITICAL_SECTION2(); | ||
return r ? NULL : Py_NewRef(self); | ||
} | ||
|
||
PyDoc_STRVAR(getitem__doc__, | ||
|
@@ -4610,14 +4641,15 @@ PyTypeObject PyDictRevIterKey_Type = { | |
|
||
|
||
/*[clinic input] | ||
@critical_section | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want a critical section here. Creating the reverse iterator is thread-safe without any special handling, although iterating over the reverse iterator may require special handling. |
||
dict.__reversed__ | ||
|
||
Return a reverse iterator over the dict keys. | ||
[clinic start generated code]*/ | ||
|
||
static PyObject * | ||
dict___reversed___impl(PyDictObject *self) | ||
/*[clinic end generated code: output=e674483336d1ed51 input=23210ef3477d8c4d]*/ | ||
/*[clinic end generated code: output=e674483336d1ed51 input=6123e7c956063d86]*/ | ||
{ | ||
assert (PyDict_Check(self)); | ||
return dictiter_new(self, &PyDictRevIterKey_Type); | ||
|
@@ -5246,7 +5278,11 @@ PyTypeObject PyDictKeys_Type = { | |
static PyObject * | ||
dictkeys_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
return _PyDictView_New(dict, &PyDictKeys_Type); | ||
PyObject *view; | ||
Py_BEGIN_CRITICAL_SECTION(dict); | ||
view = _PyDictView_New(dict, &PyDictKeys_Type); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No critical section here. Creating the view objects, like |
||
Py_END_CRITICAL_SECTION(); | ||
return view; | ||
} | ||
|
||
static PyObject * | ||
|
@@ -5348,7 +5384,11 @@ PyTypeObject PyDictItems_Type = { | |
static PyObject * | ||
dictitems_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
return _PyDictView_New(dict, &PyDictItems_Type); | ||
PyObject *view; | ||
Py_BEGIN_CRITICAL_SECTION(dict); | ||
view = _PyDictView_New(dict, &PyDictItems_Type); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No critical section here. |
||
Py_END_CRITICAL_SECTION(); | ||
return view; | ||
} | ||
|
||
static PyObject * | ||
|
@@ -5429,7 +5469,11 @@ PyTypeObject PyDictValues_Type = { | |
static PyObject * | ||
dictvalues_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) | ||
{ | ||
return _PyDictView_New(dict, &PyDictValues_Type); | ||
PyObject *view; | ||
Py_BEGIN_CRITICAL_SECTION(dict); | ||
view = _PyDictView_New(dict, &PyDictValues_Type); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No critical section here |
||
Py_END_CRITICAL_SECTION(); | ||
return view; | ||
} | ||
|
||
static PyObject * | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment like:
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()