Files
cleveragents-core/features/context_tier_thread_safety.feature
HAL9000 0f75429253 fix(concurrency): add thread safety to ContextTierService
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
2026-04-15 00:24:13 +00:00

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