From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:31:05 +0000 (+0100) Subject: [3.14] gh-142495: Make `defaultdict` keep existed value when racing with `__missing__... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d089cad6564f6ef713b5acab85cb42b28611f5e8;p=thirdparty%2FPython%2Fcpython.git [3.14] gh-142495: Make `defaultdict` keep existed value when racing with `__missing__` (GH-142668) (GH-142832) (cherry picked from commit a0434075108efe6acdfba34f42545f4d80ac9a5e) Co-authored-by: Edward Xu --- diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index bdbe9b81e8fb..fbd7354a915a 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -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 index 000000000000..3e1a624fe564 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-13-23-26-42.gh-issue-142495.I88Uv_.rst @@ -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. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 3ba48d5d9d3c..3b14a21fa842 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2231,11 +2231,11 @@ defdict_missing(PyObject *op, PyObject *key) value = _PyObject_CallNoArgs(factory); if (value == NULL) return value; - if (PyObject_SetItem(op, key, value) < 0) { - Py_DECREF(value); - return NULL; - } - return value; + PyObject *result = NULL; + (void)PyDict_SetDefaultRef(op, key, value, &result); + // 'result' is NULL, or a strong reference to 'value' or 'op[key]' + Py_DECREF(value); + return result; } static inline PyObject*