Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Lib/test/test_free_threading/test_defaultdict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import unittest
from collections import defaultdict

from threading import Barrier, Thread
from unittest import TestCase

try:
import _testcapi
except ImportError:
_testcapi = None

from test.support import threading_helper

@threading_helper.requires_working_threading()
class TestDefaultDict(TestCase):
def test_default_factory_race(self):
wait_barrier = Barrier(2)
write_barrier = Barrier(2)
key = "default_race_key"

def default_factory():
wait_barrier.wait()
write_barrier.wait()
return "default_value"

test_dict = defaultdict(default_factory)

def writer():
wait_barrier.wait()
test_dict[key] = "writer_value"
write_barrier.wait()

default_factory_thread = Thread(target=lambda: test_dict[key])
writer_thread = Thread(target=writer)
default_factory_thread.start()
writer_thread.start()
default_factory_thread.join()
writer_thread.join()
self.assertEqual(test_dict[key], "writer_value")


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
: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 by another thread.
10 changes: 5 additions & 5 deletions Modules/_collectionsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't need the return value, could we use PyDict_SetDefault?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyDict_SetDefault returns a borrowed reference and I find this easier to read (at least the result is know to be a strong reference and we can directly return it).

// 'result' is NULL, or a strong reference to 'value' or 'op[key]'
Py_DECREF(value);
return result;
}

static inline PyObject*
Expand Down
Loading