957c6c622e
Replace {col}/{col:d} curly-brace column refs with Behave-required <col>
angle-bracket syntax in all ScenarioOutline step text. Rename ambiguous
given step to avoid duplicate step definition conflicts. Fix min_ -> min_count
parameter mismatch, move _error_mutex init before thread start to eliminate
lock race, apply ruff format wraps, and add get_invariant() call in snapshot
step to cover invariant_service.py:560-561.
ISSUES CLOSED: #7524
76 lines
3.2 KiB
Gherkin
76 lines
3.2 KiB
Gherkin
# This test captures the concurrency bug discovered in issue #7524.
|
|
#
|
|
# InvariantService stored invariants (_invariants dict) and enforcement records
|
|
# (_enforcement_records list) without any threading.Lock. Concurrent calls from
|
|
# parallel plan execution could raise RuntimeError: dictionary changed size during
|
|
# iteration or silently corrupt data.
|
|
#
|
|
# The fix adds a threading.RLock to InvariantService guarding all shared mutable
|
|
# state (see commit that implements the fix).
|
|
|
|
@tdd_issue @tdd_issue_7524
|
|
Feature: TDD Issue #7524 - InvariantService Thread Safety
|
|
InvariantService must be thread-safe so that concurrent calls from parallel
|
|
plan execution threads cannot race on _invariants or _enforcement_records.
|
|
|
|
Scenario Outline: Concurrent add_invariant() must not corrupt the dict
|
|
Given an invariant service for thread-safety testing
|
|
When <n> threads concurrently add <count> invariants through a barrier
|
|
Then the service has no RuntimeError raised during concurrent access
|
|
And the service should have exactly <total> active invariants
|
|
And all added invariants should be retrievable via get_invariant_snapshot
|
|
|
|
Examples:
|
|
| n | count | total |
|
|
| 2 | 5 | 10 |
|
|
| 4 | 5 | 20 |
|
|
| 8 | 3 | 24 |
|
|
| 10| 2 | 20 |
|
|
|
|
Scenario Outline: Concurrent list_invariants() must not raise during iteration
|
|
Given an invariant service with <count> pre-existing invariants
|
|
When 5 threads concurrently list all invariants through a barrier
|
|
Then no thread raised RuntimeError during concurrent reads
|
|
And the caller should receive a valid list of at least <min_count> active invariants
|
|
|
|
Examples:
|
|
| count | min_count |
|
|
| 10 | 10 |
|
|
| 20 | 20 |
|
|
| 5 | 5 |
|
|
|
|
Scenario Outline: Concurrent remove_invariant() must not race with add
|
|
Given an invariant service for thread-safety testing
|
|
When <n> threads concurrently add invariants while <m> concurrent threads remove different ones through a barrier
|
|
Then no thread raised RuntimeError during mixed access
|
|
And the service should have a consistent count of active invariants
|
|
|
|
Examples:
|
|
| n | m |
|
|
| 5 | 2 |
|
|
| 4 | 3 |
|
|
| 8 | 4 |
|
|
|
|
Scenario Outline: Concurrent enforce_invariants() must not corrupt the record list
|
|
Given an invariant service with <count> invariants to enforce
|
|
When <n> threads concurrently call enforce_invariants on the same set through a barrier
|
|
Then no thread raised RuntimeError during enforcement
|
|
And each thread should have received exactly <total> enforcement records
|
|
|
|
Examples:
|
|
| count | n | total |
|
|
| 3 | 4 | 3 |
|
|
| 5 | 8 | 5 |
|
|
| 2 | 10 | 2 |
|
|
|
|
Scenario Outline: Concurrent enforce and list must both succeed
|
|
Given an invariant service with <count> invariants already enforced by a previous thread
|
|
When <n> threads concurrently enforce new invariants while other threads list them through a barrier
|
|
Then no thread raised RuntimeError during mixed enforcement and listing
|
|
And the enforcement record count should be consistent across all threads
|
|
|
|
Examples:
|
|
| count | n |
|
|
| 3 | 4 |
|
|
| 5 | 6 |
|