Skip to content
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

Fix race condition inside recursive strategies #2783

Merged
merged 1 commit into from
Jan 27, 2021
Merged
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
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