Skip to content

GH-116738: document thread-safety of bisect #136555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions Doc/library/bisect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +27 to +35
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.. 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.
.. 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:
Expand Down
79 changes: 79 additions & 0 deletions Lib/test/test_free_threading/test_bisect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import unittest
from test.support import import_helper, threading_helper
from threading import Thread, Barrier
import random

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()
Loading