Skip to content

Commit e2b2cb9

Browse files
Евгений БлиновЕвгений Блинов
authored andcommitted
Add thread safety checks and fix field conflict test
1 parent 789e807 commit e2b2cb9

3 files changed

Lines changed: 32 additions & 5 deletions

File tree

tests/thread_safety/conftest.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ def acquire(self) -> None:
5555
self._owner_thread_id = thread_id
5656

5757
def release(self) -> None:
58+
thread_id = get_ident()
5859
with self._state_lock:
60+
if self._owner_thread_id != thread_id:
61+
raise RuntimeError('Release from a non-owner thread was detected.')
5962
self._owner_thread_id = None
6063
self._lock.release()
6164

@@ -110,7 +113,10 @@ def callback(old_value: Any, new_value: Any, storage: Any) -> None: # noqa: ARG
110113
entered_event.set()
111114
if exception is not None:
112115
raise exception
113-
release_event.wait()
116+
if not release_event.wait(DEFAULT_THREAD_TIMEOUT):
117+
raise TimeoutError(
118+
f'Gate callback "{label}" was not released within {DEFAULT_THREAD_TIMEOUT} seconds.',
119+
)
114120
event_log.append(f'{label}:exit')
115121

116122
return callback
@@ -121,14 +127,26 @@ def replace_field_lock_with_locklib_lock(
121127
field_name: str,
122128
expected_group_fields: List[str],
123129
) -> LocklibDeadlockDetectingLock:
130+
if field_name not in instance.__locks__:
131+
raise RuntimeError(f'The "{field_name}" field is missing from instance.__locks__.')
132+
124133
old_lock = instance.__locks__[field_name]
125134
new_lock = LocklibDeadlockDetectingLock()
135+
replaced_fields = []
126136

127137
for current_field_name, current_lock in list(instance.__locks__.items()):
128138
if current_lock is old_lock:
129139
instance.__locks__[current_field_name] = new_lock
140+
replaced_fields.append(current_field_name)
141+
142+
if not replaced_fields:
143+
raise RuntimeError(f'No fields were rebound for the "{field_name}" lock group.')
130144

131145
for expected_field_name in expected_group_fields:
146+
if expected_field_name not in instance.__locks__:
147+
raise RuntimeError(
148+
f'The expected field "{expected_field_name}" is missing from instance.__locks__.',
149+
)
132150
if instance.__locks__[expected_field_name] is not new_lock:
133151
raise RuntimeError(
134152
f'The "{expected_field_name}" field was not rebound to the expected lock group.',

tests/thread_safety/fields/test_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ def reader() -> None:
336336
while not read_values.empty():
337337
collected_reads.append(read_values.get_nowait())
338338

339+
assert len(collected_reads) == num_readers * iterations
339340
assert set(collected_reads).issubset({0, 1, 2, 3, 4, 5})
340341

341342

tests/thread_safety/test_storage.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@ class SomeClass(Storage):
1818

1919

2020
def test_share_mutex_with_and_conflicts_merge_into_one_group():
21+
def fields_conflict(
22+
_old_first: int,
23+
new_first: int,
24+
_old_third: int,
25+
new_third: int,
26+
) -> bool:
27+
return new_first > 0 and new_third > 0
28+
2129
class SomeClass(Storage):
2230
first_field = Field(
23-
1,
31+
0,
2432
share_mutex_with=['second_field'],
25-
conflicts={'third_field': lambda *_: False},
33+
conflicts={'third_field': fields_conflict},
2634
)
27-
second_field = Field(2)
28-
third_field = Field(3)
35+
second_field = Field(0)
36+
third_field = Field(0)
2937

3038
instance = SomeClass()
3139

0 commit comments

Comments
 (0)