0f75429253
Implemented thread-safety improvements for ContextTierService by introducing a re-entrant lock and guarding all critical sections with self._lock. This prevents RuntimeError: dictionary changed size during iteration under concurrent plan execution. - Added threading.RLock to ContextTierService.__init__ as self._lock - Wrapped all public methods (store, get, promote, demote, evict_lru, get_metrics, get_all_fragments, get_hot_fragments, get_for_actor, get_scoped_view) with with self._lock: - Added _lock: threading.RLock type stub to TierRuntimeMixin and ScopedTierMixin - Wrapped enforce_staleness in TierRuntimeMixin with self._lock - Wrapped get_scoped_by_resource and get_scoped_metrics in ScopedTierMixin with self._lock - Extracted settings helpers to new context_tier_settings.py to keep context_tiers.py under 500 lines - Added BDD feature file context_tier_thread_safety.feature with 10 thread-safety scenarios - Added step definitions context_tier_thread_safety_steps.py - Updated CHANGELOG.md with fix entry ISSUES CLOSED: #7547
85 lines
4.0 KiB
Gherkin
85 lines
4.0 KiB
Gherkin
@mock_only
|
|
Feature: ContextTierService thread safety (#7547)
|
|
As an ACMS developer
|
|
I want ContextTierService to be safe for concurrent plan execution
|
|
So that parallel subplans sharing the same singleton instance do not
|
|
cause RuntimeError: dictionary changed size during iteration or
|
|
data corruption in the hot/warm/cold tier dicts
|
|
|
|
Background:
|
|
Given a thread-safe context tier service
|
|
|
|
# -----------------------------------------------------------------
|
|
# Basic lock presence
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: ContextTierService has a reentrant lock attribute
|
|
Then the tier service should have a _lock attribute
|
|
And the _lock should be a threading.RLock
|
|
|
|
# -----------------------------------------------------------------
|
|
# Concurrent store operations
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Concurrent store from multiple threads does not raise RuntimeError
|
|
When 10 threads concurrently store 5 fragments each into the tier service
|
|
Then no RuntimeError should have been raised during concurrent store
|
|
And the tier service should contain at least 1 fragment
|
|
|
|
Scenario: Concurrent store and get from multiple threads is data-race free
|
|
Given 20 fragments pre-stored in the tier service
|
|
When 5 threads concurrently store fragments and 5 threads concurrently get fragments
|
|
Then no exception should have been raised during concurrent store and get
|
|
|
|
# -----------------------------------------------------------------
|
|
# Concurrent promote / demote
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Concurrent promote from multiple threads does not corrupt tier state
|
|
Given 10 fragments stored in the cold tier
|
|
When 5 threads concurrently promote all cold fragments
|
|
Then no exception should have been raised during concurrent promote
|
|
And no fragment should appear in more than one tier
|
|
|
|
Scenario: Concurrent demote from multiple threads does not corrupt tier state
|
|
Given 10 fragments stored in the hot tier
|
|
When 5 threads concurrently demote all hot fragments
|
|
Then no exception should have been raised during concurrent demote
|
|
And no fragment should appear in more than one tier
|
|
|
|
# -----------------------------------------------------------------
|
|
# Concurrent evict_lru
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Concurrent evict_lru does not raise RuntimeError
|
|
Given 20 fragments stored in the hot tier
|
|
When 4 threads concurrently evict 1 LRU fragment from the hot tier
|
|
Then no exception should have been raised during concurrent evict
|
|
|
|
# -----------------------------------------------------------------
|
|
# Concurrent enforce_staleness
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Concurrent enforce_staleness does not raise RuntimeError
|
|
Given 10 fragments stored in the hot tier with stale timestamps
|
|
When 4 threads concurrently invoke enforce_staleness
|
|
Then no exception should have been raised during concurrent staleness enforcement
|
|
|
|
# -----------------------------------------------------------------
|
|
# Concurrent get_metrics
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Concurrent get_metrics does not raise RuntimeError
|
|
Given 10 fragments stored in the hot tier
|
|
When 8 threads concurrently call get_metrics while 4 threads store fragments
|
|
Then no exception should have been raised during concurrent metrics access
|
|
|
|
# -----------------------------------------------------------------
|
|
# Singleton safety: same instance shared across threads
|
|
# -----------------------------------------------------------------
|
|
|
|
Scenario: Shared singleton instance survives concurrent access from 20 threads
|
|
When 20 threads each store 3 fragments and immediately get them back
|
|
Then no exception should have been raised during concurrent singleton access
|
|
And the tier service metrics should reflect a non-negative fragment count
|