Private
Public Access
0
0

fix(provider_state): change Lock to RLock to prevent re-entrant deadlock

TIER-3 READ AGENTS.md + conductor/code_styleguides/error_handling.md + src/provider_state.py + src/ai_client.py:2148-2220 before provider-state-rlock-fix.

Tier 2's 25a22057 commit re-bound the 14 module globals in src/ai_client.py as
aliases to provider_state.get_history(...) instances. The ProviderHistory dunder
methods (__bool__, __len__, __iter__, __getitem__) all use \with self.lock:\.

The dunders are non-reentrant: \	hreading.Lock\ blocks if the lock is already
held. The call site in src/ai_client.py:2210-2217 acquires the lock via
\with _deepseek_history_lock:\ (alias to ProviderHistory.lock), then calls
_rerepair_deepseek_history(_deepseek_history) which does \history[-1]\
(acquires the lock again -> DEADLOCK). This caused
tests/test_deepseek_provider.py::test_deepseek_completion_logic to hang
with a 30s timeout.

Fix: change \	hreading.Lock\ to \	hreading.RLock\ in ProviderHistory.
The dunders can now be safely called while the lock is already held.

Also removed:
- Duplicate @dataclass decorator on ProviderHistory (line 25-26)
- Duplicate _PROVIDER_HISTORIES dict declaration (lines 64-71 and 74-81)

Acceptance: test_deepseek_provider (7/7) + test_provider_state + test_ai_client_result + test_ai_client_tool_loop all pass.
This commit is contained in:
2026-06-24 23:30:15 -04:00
parent 33569e1ce5
commit cc7993e53d
+1 -12
View File
@@ -22,11 +22,10 @@ from dataclasses import dataclass, field
from src.type_aliases import HistoryMessage, Metadata
@dataclass
@dataclass
class ProviderHistory:
messages: list[HistoryMessage] = field(default_factory=list)
lock: threading.Lock = field(default_factory=threading.Lock)
lock: threading.RLock = field(default_factory=threading.RLock)
def __bool__(self) -> bool:
with self.lock:
@@ -71,16 +70,6 @@ _PROVIDER_HISTORIES: dict[str, ProviderHistory] = {
}
_PROVIDER_HISTORIES: dict[str, ProviderHistory] = {
"anthropic": ProviderHistory(),
"deepseek": ProviderHistory(),
"minimax": ProviderHistory(),
"qwen": ProviderHistory(),
"grok": ProviderHistory(),
"llama": ProviderHistory(),
}
def get_history(provider: str) -> ProviderHistory:
if provider not in _PROVIDER_HISTORIES:
raise KeyError(f"Unknown provider: {provider!r}")