]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-142495: Make `defaultdict` keep existed value when racing with `__missing__...
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 17 Dec 2025 08:40:47 +0000 (10:40 +0200)
committerGitHub <noreply@github.com>
Wed, 17 Dec 2025 08:40:47 +0000 (08:40 +0000)
(cherry picked from commit a0434075108efe6acdfba34f42545f4d80ac9a5e)

Co-authored-by: Edward Xu <xuxiangad@gmail.com>
Lib/test/test_defaultdict.py
Misc/NEWS.d/next/Library/2025-12-13-23-26-42.gh-issue-142495.I88Uv_.rst [new file with mode: 0644]
Modules/_collectionsmodule.c

index bdbe9b81e8fb3f5a0786478ef1c01d1ee1aea291..fbd7354a915a0a001a2c7ed8a976a82315be71d3 100644 (file)
@@ -186,5 +186,23 @@ class TestDefaultDict(unittest.TestCase):
         with self.assertRaises(TypeError):
             i |= None
 
+    def test_factory_conflict_with_set_value(self):
+        key = "conflict_test"
+        count = 0
+
+        def default_factory():
+            nonlocal count
+            count += 1
+            local_count = count
+            if count == 1:
+                test_dict[key]
+            return local_count
+
+        test_dict = defaultdict(default_factory)
+
+        self.assertEqual(count, 0)
+        self.assertEqual(test_dict[key], 2)
+        self.assertEqual(count, 2)
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2025-12-13-23-26-42.gh-issue-142495.I88Uv_.rst b/Misc/NEWS.d/next/Library/2025-12-13-23-26-42.gh-issue-142495.I88Uv_.rst
new file mode 100644 (file)
index 0000000..3e1a624
--- /dev/null
@@ -0,0 +1,4 @@
+:class:`collections.defaultdict` now prioritizes :meth:`~object.__setitem__`
+when inserting default values from ``default_factory``. This prevents race
+conditions where a default value would overwrite a value set before
+``default_factory`` returns.
index 2c30e5c70a6cc2fa5317e20bd8ca4de0e8c6890c..b356c917c1d5efba63d16c97de0415c0db39f939 100644 (file)
@@ -2203,11 +2203,11 @@ defdict_missing(defdictobject *dd, PyObject *key)
     value = _PyObject_CallNoArgs(factory);
     if (value == NULL)
         return value;
-    if (PyObject_SetItem((PyObject *)dd, key, value) < 0) {
-        Py_DECREF(value);
-        return NULL;
-    }
-    return value;
+    PyObject *result = NULL;
+    (void)PyDict_SetDefaultRef((PyObject *)dd, key, value, &result);
+    // 'result' is NULL, or a strong reference to 'value' or 'dd[key]'
+    Py_DECREF(value);
+    return result;
 }
 
 static inline PyObject*