@@ -121,6 +121,7 @@ As a consequence of this, split keys have a maximum size of 16.
121121#include "pycore_ceval.h" // _PyEval_GetBuiltin()
122122#include "pycore_code.h" // stats
123123#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION
124+ #include "pycore_descrobject.h" // _PyDictProxy_GetMapping()
124125#include "pycore_dict.h" // export _PyDict_SizeOf()
125126#include "pycore_freelist.h" // _PyFreeListState_GET()
126127#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
@@ -4305,11 +4306,21 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43054306
43064307 PyDictObject * mp = _PyAnyDict_CAST (a );
43074308
4309+ /* Mapping proxies (including type.__dict__) wrap a real dict. Unwrap
4310+ * so we take the locked dict-to-dict path instead of iterating the
4311+ * proxy without holding the underlying dict's critical section.
4312+ * See gh-157217.
4313+ */
4314+ PyObject * source = b ;
4315+ if (PyObject_TypeCheck (b , & PyDictProxy_Type )) {
4316+ source = _PyDictProxy_GetMapping (b );
4317+ }
4318+
43084319 int res = 0 ;
4309- if (PyAnyDict_Check (b ) && (Py_TYPE (b )-> tp_iter == dict_iter )) {
4310- PyDictObject * other = (PyDictObject * )b ;
4320+ if (PyAnyDict_Check (source ) && (Py_TYPE (source )-> tp_iter == dict_iter )) {
4321+ PyDictObject * other = (PyDictObject * )source ;
43114322 int res ;
4312- Py_BEGIN_CRITICAL_SECTION2 (a , b );
4323+ Py_BEGIN_CRITICAL_SECTION2 (a , source );
43134324 assert (can_modify_dict (mp ));
43144325 res = dict_dict_merge ((PyDictObject * )a , other , override , dupkey );
43154326 ASSERT_CONSISTENT (a );
0 commit comments