https://github.com/python/cpython/commit/1243cd677d7f62b90c50546c5ff27e26ef24ca4f
commit: 1243cd677d7f62b90c50546c5ff27e26ef24ca4f
branch: 3.14
author: Saul Cooperman <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-05-19T05:34:06-07:00
summary:
[3.14] gh-146452: Improve locking granularity in pickle's batch_dict_… (#150062)
[3.14] gh-146452: Improve locking granularity in pickle's batch_dict_exact and
fix race condition (GH-150025)
Remove assertion that could fail in rare race condition.
Replace the coarse critical section wrapping the entire function with
fine-grained sections covering only PyDict_Next + Py_INCREF.
Also handle PyDict_Next returning 0 in the single-item fast path.
(cherry picked from commit 57a0e570d36f41b953a91bbaf4262a5d05d0391b)
files:
A Misc/NEWS.d/next/Library/2026-05-18-15-30-34.gh-issue-146452.RM0EVJ.rst
M Modules/_pickle.c
diff --git
a/Misc/NEWS.d/next/Library/2026-05-18-15-30-34.gh-issue-146452.RM0EVJ.rst
b/Misc/NEWS.d/next/Library/2026-05-18-15-30-34.gh-issue-146452.RM0EVJ.rst
new file mode 100644
index 00000000000000..66f9acf6c710a7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-18-15-30-34.gh-issue-146452.RM0EVJ.rst
@@ -0,0 +1,2 @@
+Fix race condition when pickling dictionaries in free threaded builds. Also
+reduce critical section cover.
diff --git a/Modules/_pickle.c b/Modules/_pickle.c
index 75e1c4dea85e00..201ea9af8b2a8b 100644
--- a/Modules/_pickle.c
+++ b/Modules/_pickle.c
@@ -3351,9 +3351,12 @@ batch_dict(PickleState *state, PicklerObject *self,
PyObject *iter, PyObject *or
* Returns 0 on success, -1 on error.
*
* Note that this currently doesn't work for protocol 0.
+
+ * gh-146452: Wrap the dict iteration in a critical sections to prevent
+ * concurrent mutation from invalidating PyDict_Next() iteration state.
*/
static int
-batch_dict_exact_impl(PickleState *state, PicklerObject *self, PyObject *obj)
+batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
{
PyObject *key = NULL, *value = NULL;
int i;
@@ -3370,9 +3373,19 @@ batch_dict_exact_impl(PickleState *state, PicklerObject
*self, PyObject *obj)
/* Special-case len(d) == 1 to save space. */
if (dict_size == 1) {
- PyDict_Next(obj, &ppos, &key, &value);
- Py_INCREF(key);
- Py_INCREF(value);
+ int next;
+ Py_BEGIN_CRITICAL_SECTION(obj);
+ next = PyDict_Next(obj, &ppos, &key, &value);
+ if (next) {
+ Py_INCREF(key);
+ Py_INCREF(value);
+ }
+ Py_END_CRITICAL_SECTION();
+ if (!next) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "dictionary changed size during iteration");
+ goto error;
+ }
if (save(state, self, key, 0) < 0) {
goto error;
}
@@ -3392,9 +3405,18 @@ batch_dict_exact_impl(PickleState *state, PicklerObject
*self, PyObject *obj)
i = 0;
if (_Pickler_Write(self, &mark_op, 1) < 0)
return -1;
- while (PyDict_Next(obj, &ppos, &key, &value)) {
- Py_INCREF(key);
- Py_INCREF(value);
+ int next;
+ while (1) {
+ Py_BEGIN_CRITICAL_SECTION(obj);
+ next = PyDict_Next(obj, &ppos, &key, &value);
+ if (next) {
+ Py_INCREF(key);
+ Py_INCREF(value);
+ }
+ Py_END_CRITICAL_SECTION();
+ if (!next) {
+ break;
+ }
if (save(state, self, key, 0) < 0) {
goto error;
}
@@ -3424,18 +3446,6 @@ batch_dict_exact_impl(PickleState *state, PicklerObject
*self, PyObject *obj)
return -1;
}
-/* gh-146452: Wrap the dict iteration in a critical section to prevent
- concurrent mutation from invalidating PyDict_Next() iteration state. */
-static int
-batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
-{
- int ret;
- Py_BEGIN_CRITICAL_SECTION(obj);
- ret = batch_dict_exact_impl(state, self, obj);
- Py_END_CRITICAL_SECTION();
- return ret;
-}
-
static int
save_dict(PickleState *state, PicklerObject *self, PyObject *obj)
{
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]