]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-112075: Remove critical section in dict.get (gh-129336)
authorPieter Eendebak <pieter.eendebak@gmail.com>
Tue, 28 Jan 2025 21:55:45 +0000 (22:55 +0100)
committerGitHub <noreply@github.com>
Tue, 28 Jan 2025 21:55:45 +0000 (21:55 +0000)
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
Objects/clinic/dictobject.c.h
Objects/dictobject.c

index 13717cb39fa35d277ce2a053ec71a6b166b8ecff..4f605e0c51f0d5f627f84ae6ac552ae74843c47f 100644 (file)
@@ -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
index cdf39ce147203b69a407aea5f761b26b95be7070..c66916bb33aa372b6440be559a61a9a318e6e69a 100644 (file)
@@ -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]*/
index 8fe71123252a75352f146e49abefb96e9f19be72..733a10a2e80b18591e1f4a9402aab4a6d5f75936 100644 (file)
@@ -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;