From cc7993e53ddd135c313206ae41d7c7ed67c57a58 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 24 Jun 2026 23:30:15 -0400 Subject: [PATCH] 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. --- src/provider_state.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/provider_state.py b/src/provider_state.py index c1302b22..d988f3de 100644 --- a/src/provider_state.py +++ b/src/provider_state.py @@ -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}")