Files
cleveragents-core/features/invariant_service_thread_safety.feature
HAL9000 957c6c622e fix(tests): correct ScenarioOutline syntax and step parameter mismatches
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
2026-06-17 22:36:33 -04:00

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 |