]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] GH-116738: document thread-safety of bisect (GH-136555) (#137222)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 30 Jul 2025 11:50:39 +0000 (13:50 +0200)
committerGitHub <noreply@github.com>
Wed, 30 Jul 2025 11:50:39 +0000 (11:50 +0000)
* GH-116738: document thread-safety of bisect (GH-136555)
(cherry picked from commit 5236b0281b91a874b14cf15f3fdef9b7beffb22f)

Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Doc/library/bisect.rst
Lib/test/test_free_threading/test_bisect.py [new file with mode: 0644]

index 78da563397b6255139f23d03502867cdcee384fc..d02ffe469adb1a12fb9a6c754aa64a69ca74e764 100644 (file)
@@ -24,6 +24,16 @@ method to determine whether a value has been found.  Instead, the
 functions only call the :meth:`~object.__lt__` method and will return an insertion
 point between values in an array.
 
+.. note::
+
+   The functions in this module are not thread-safe. If multiple threads
+   concurrently use :mod:`bisect` functions on the same sequence, this
+   may result in undefined behaviour. Likewise, if the provided sequence
+   is mutated by a different thread while a :mod:`bisect` function
+   is operating on it, the result is undefined. For example, using
+   :py:func:`~bisect.insort_left` on the same list from multiple threads
+   may result in the list becoming unsorted.
+
 .. _bisect functions:
 
 The following functions are provided:
diff --git a/Lib/test/test_free_threading/test_bisect.py b/Lib/test/test_free_threading/test_bisect.py
new file mode 100644 (file)
index 0000000..b8645d1
--- /dev/null
@@ -0,0 +1,79 @@
+import unittest
+from test.support import import_helper, threading_helper
+import random
+from threading import Thread, Barrier
+
+py_bisect = import_helper.import_fresh_module('bisect', blocked=['_bisect'])
+c_bisect = import_helper.import_fresh_module('bisect', fresh=['_bisect'])
+
+
+NTHREADS = 4
+OBJECT_COUNT = 500
+
+
+class TestBase:
+    def do_racing_insort(self, insert_method):
+        def insert(data):
+            for _ in range(OBJECT_COUNT):
+                x = random.randint(-OBJECT_COUNT, OBJECT_COUNT)
+                insert_method(data, x)
+
+        data = list(range(OBJECT_COUNT))
+        self.run_concurrently(
+            worker_func=insert, args=(data,), nthreads=NTHREADS
+        )
+        if False:
+            # These functions are not thread-safe and so the list can become
+            # unsorted.  However, we don't want Python to crash if these
+            # functions are used concurrently on the same sequence.  This
+            # should also not produce any TSAN warnings.
+            self.assertTrue(self.is_sorted_ascending(data))
+
+    def test_racing_insert_right(self):
+        self.do_racing_insort(self.mod.insort_right)
+
+    def test_racing_insert_left(self):
+        self.do_racing_insort(self.mod.insort_left)
+
+    @staticmethod
+    def is_sorted_ascending(lst):
+        """
+        Check if the list is sorted in ascending order (non-decreasing).
+        """
+        return all(lst[i - 1] <= lst[i] for i in range(1, len(lst)))
+
+    def run_concurrently(self, worker_func, args, nthreads):
+        """
+        Run the worker function concurrently in multiple threads.
+        """
+        barrier = Barrier(nthreads)
+
+        def wrapper_func(*args):
+            # Wait for all threads to reach this point before proceeding.
+            barrier.wait()
+            worker_func(*args)
+
+        with threading_helper.catch_threading_exception() as cm:
+            workers = (
+                Thread(target=wrapper_func, args=args)
+                for _ in range(nthreads)
+            )
+            with threading_helper.start_threads(workers):
+                pass
+
+            # Worker threads should not raise any exceptions
+            self.assertIsNone(cm.exc_value)
+
+
+@threading_helper.requires_working_threading()
+class TestPyBisect(unittest.TestCase, TestBase):
+    mod = py_bisect
+
+
+@threading_helper.requires_working_threading()
+class TestCBisect(unittest.TestCase, TestBase):
+    mod = c_bisect
+
+
+if __name__ == "__main__":
+    unittest.main()