Skip to content

Commit

Permalink
Fix race condition inside recursive strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
Stranger6667 committed Jan 27, 2021
1 parent 06fdabd commit e0025af
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 3 deletions.
5 changes: 5 additions & 0 deletions hypothesis-python/RELEASE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RELEASE_TYPE: patch

This release prevents a race condition inside :func:`~hypothesis.strategies.recursive` strategies.
The race condition occurs when the same :func:`~hypothesis.strategies.recursive` strategy is shared among tests
that are running in multiple threads (:issue:`2717`).
20 changes: 18 additions & 2 deletions hypothesis-python/src/hypothesis/strategies/_internal/recursive.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#
# END HEADER

import threading
from contextlib import contextmanager

from hypothesis.errors import InvalidArgument
Expand All @@ -29,8 +30,23 @@ class LimitedStrategy(SearchStrategy):
def __init__(self, strategy):
super().__init__()
self.base_strategy = strategy
self.marker = 0
self.currently_capped = False
self._threadlocal = threading.local()

@property
def marker(self):
return getattr(self._threadlocal, "marker", 0)

@marker.setter
def marker(self, value):
self._threadlocal.marker = value

@property
def currently_capped(self):
return getattr(self._threadlocal, "currently_capped", False)

@currently_capped.setter
def currently_capped(self, value):
self._threadlocal.currently_capped = value

def __repr__(self):
return "LimitedStrategy(%r)" % (self.base_strategy,)
Expand Down
30 changes: 29 additions & 1 deletion hypothesis-python/tests/nocover/test_recursive.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#
# END HEADER

from hypothesis import settings, strategies as st
import threading

from hypothesis import given, settings, strategies as st
from tests.common.debug import find_any, minimal
from tests.common.utils import flaky

Expand Down Expand Up @@ -127,3 +129,29 @@ def test_can_form_sets_of_recursive_data():
)
xs = minimal(trees, lambda x: len(x) >= size, timeout_after=None)
assert len(xs) == size


def test_drawing_from_recursive_strategy_is_thread_safe():
shared_strategy = st.recursive(st.integers(), lambda s: st.lists(s, max_size=3))

errors = []

@given(data=st.data())
def test(data):
try:
data.draw(shared_strategy)
except Exception as exc:
errors.append(exc)

threads = []

for _ in range(4):
threads.append(threading.Thread(target=test))

for thread in threads:
thread.start()

for thread in threads:
thread.join()

assert not errors

0 comments on commit e0025af

Please sign in to comment.