From 64c417dee5594c882beac03e7d2942ca05b5c204 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 28 Jan 2025 22:55:45 +0100 Subject: gh-112075: Remove critical section in dict.get (gh-129336) The `dict.get` implementation uses `_Py_dict_lookup_threadsafe`, which is thread-safe, so we remove the critical section from the argument clinic. Add a test for concurrent dict get and set operations. --- Lib/test/test_free_threading/test_dict.py | 23 ++++++++++++++++++++++- Objects/clinic/dictobject.c.h | 4 +--- Objects/dictobject.c | 3 +-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_free_threading/test_dict.py b/Lib/test/test_free_threading/test_dict.py index 13717cb..4f605e0 100644 --- a/Lib/test/test_free_threading/test_dict.py +++ b/Lib/test/test_free_threading/test_dict.py @@ -5,7 +5,7 @@ import weakref from ast import Or from functools import partial -from threading import Thread +from threading import Barrier, Thread from unittest import TestCase try: @@ -142,6 +142,27 @@ class TestDict(TestCase): for ref in thread_list: self.assertIsNone(ref()) + def test_racing_get_set_dict(self): + """Races getting and setting a dict should be thread safe""" + THREAD_COUNT = 10 + barrier = Barrier(THREAD_COUNT) + def work(d): + barrier.wait() + for _ in range(1000): + d[10] = 0 + d.get(10, None) + _ = d[10] + + d = {} + worker_threads = [] + for ii in range(THREAD_COUNT): + worker_threads.append(Thread(target=work, args=[d])) + for t in worker_threads: + t.start() + for t in worker_threads: + t.join() + + def test_racing_set_object_dict(self): """Races assigning to __dict__ should be thread safe""" class C: pass diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index cdf39ce..c66916b 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -94,9 +94,7 @@ dict_get(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } default_value = args[1]; skip_optional: - Py_BEGIN_CRITICAL_SECTION(self); return_value = dict_get_impl((PyDictObject *)self, key, default_value); - Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -312,4 +310,4 @@ dict_values(PyObject *self, PyObject *Py_UNUSED(ignored)) { return dict_values_impl((PyDictObject *)self); } -/*[clinic end generated code: output=4956c5b276ea652f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0f04bf0e7e6b130f input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 8fe7112..733a10a 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4248,7 +4248,6 @@ dict___contains__(PyDictObject *self, PyObject *key) } /*[clinic input] -@critical_section dict.get key: object @@ -4260,7 +4259,7 @@ Return the value for key if key is in the dictionary, else default. static PyObject * dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=bba707729dee05bf input=a631d3f18f584c60]*/ +/*[clinic end generated code: output=bba707729dee05bf input=279ddb5790b6b107]*/ { PyObject *val = NULL; Py_hash_t hash; -- cgit v0.12