https://github.com/python/cpython/commit/e62a61177f8b793d787e337034a740ca75c1ab44
commit: e62a61177f8b793d787e337034a740ca75c1ab44
branch: main
author: Farhan Saif <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-05-17T14:35:27+05:30
summary:

gh-146452: Fix pickle segfault on concurrent mutation of dict in pickle 
(#146470)

Co-authored-by: Kumar Aditya <[email protected]>

files:
A Lib/test/test_free_threading/test_pickle.py
A Misc/NEWS.d/next/Library/2026-03-26-09-30-00.gh-issue-146452.Y2N6qZ8J.rst
M Modules/_pickle.c

diff --git a/Lib/test/test_free_threading/test_pickle.py 
b/Lib/test/test_free_threading/test_pickle.py
new file mode 100644
index 00000000000000..85a644dc72ecb4
--- /dev/null
+++ b/Lib/test/test_free_threading/test_pickle.py
@@ -0,0 +1,44 @@
+import pickle
+import threading
+import unittest
+
+from test.support import threading_helper
+
+
+@threading_helper.requires_working_threading()
+class TestPickleFreeThreading(unittest.TestCase):
+
+    def test_pickle_dumps_with_concurrent_dict_mutation(self):
+        # gh-146452: Pickling a dict while another thread mutates it
+        # used to segfault. batch_dict_exact() iterated dict items via
+        # PyDict_Next() which returns borrowed references, and a
+        # concurrent pop/replace could free the value before Py_INCREF
+        # got to it.
+        shared = {str(i): list(range(20)) for i in range(50)}
+
+        def dumper():
+            for _ in range(1000):
+                try:
+                    pickle.dumps(shared)
+                except RuntimeError:
+                    # "dictionary changed size during iteration" is expected
+                    pass
+
+        def mutator():
+            for j in range(1000):
+                key = str(j % 50)
+                shared[key] = list(range(j % 20))
+                if j % 10 == 0:
+                    shared.pop(key, None)
+                    shared[key] = [j]
+
+        threads = []
+        for _ in range(10):
+            threads.append(threading.Thread(target=dumper))
+        threads.append(threading.Thread(target=mutator))
+
+        with threading_helper.start_threads(threads):
+            pass
+
+if __name__ == "__main__":
+    unittest.main()
diff --git 
a/Misc/NEWS.d/next/Library/2026-03-26-09-30-00.gh-issue-146452.Y2N6qZ8J.rst 
b/Misc/NEWS.d/next/Library/2026-03-26-09-30-00.gh-issue-146452.Y2N6qZ8J.rst
new file mode 100644
index 00000000000000..99f3cce33497a1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-26-09-30-00.gh-issue-146452.Y2N6qZ8J.rst
@@ -0,0 +1,2 @@
+Fix segfault in :mod:`pickle` when pickling a dictionary concurrently
+mutated by another thread in the free-threaded build.
diff --git a/Modules/_pickle.c b/Modules/_pickle.c
index 9874f9475ac029..7b87be23269d40 100644
--- a/Modules/_pickle.c
+++ b/Modules/_pickle.c
@@ -3452,7 +3452,7 @@ batch_dict(PickleState *state, PicklerObject *self, 
PyObject *iter, PyObject *or
  * Note that this currently doesn't work for protocol 0.
  */
 static int
-batch_dict_exact(PickleState *state, PicklerObject *self, PyObject *obj)
+batch_dict_exact_impl(PickleState *state, PicklerObject *self, PyObject *obj)
 {
     PyObject *key = NULL, *value = NULL;
     int i;
@@ -3525,6 +3525,18 @@ batch_dict_exact(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]

Reply via email to