Fix deadlock in lazy init: _ensure_initialized calling login via request()

login() called client.request() which called _ensure_initialized() which
tried to re-acquire _init_lock — deadlocking forever. Fix: set
_initializing=True while inside the init critical section so request()
skips the _ensure_initialized() guard when called from within init
(safe because query_api_info() already populated the API cache).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 15:49:26 +02:00
parent 737c816ee7
commit e13324e10c
+22 -14
View File
@@ -101,6 +101,7 @@ class DsmClient:
self._reauth_lock = asyncio.Lock() self._reauth_lock = asyncio.Lock()
self._init_lock = asyncio.Lock() self._init_lock = asyncio.Lock()
self._initialized = False self._initialized = False
self._initializing = False # True while inside _ensure_initialized
logger.debug( logger.debug(
"DsmClient: base_url=%s verify_ssl=%s timeout=%d", "DsmClient: base_url=%s verify_ssl=%s timeout=%d",
self._base_url, self._base_url,
@@ -131,21 +132,25 @@ class DsmClient:
async with self._init_lock: async with self._init_lock:
if self._initialized: # re-check inside lock if self._initialized: # re-check inside lock
return return
sys.stderr.write(f"[dsm] Connecting to {self._base_url}...\n") self._initializing = True
sys.stderr.flush() try:
logger.debug("Lazy init: querying API info from %s", self._base_url) sys.stderr.write(f"[dsm] Connecting to {self._base_url}...\n")
await self.query_api_info()
sys.stderr.write(f"[dsm] API info OK ({len(self._api_cache)} APIs)\n")
sys.stderr.flush()
if self._auth_manager:
sys.stderr.write("[dsm] Authenticating...\n")
sys.stderr.flush() sys.stderr.flush()
logger.debug("Lazy init: authenticating") logger.debug("Lazy init: querying API info from %s", self._base_url)
self._sid = await self._auth_manager.login(self) await self.query_api_info()
sys.stderr.write("[dsm] Auth OK\n") sys.stderr.write(f"[dsm] API info OK ({len(self._api_cache)} APIs)\n")
sys.stderr.flush() sys.stderr.flush()
self._initialized = True if self._auth_manager:
logger.debug("Lazy init complete") sys.stderr.write("[dsm] Authenticating...\n")
sys.stderr.flush()
logger.debug("Lazy init: authenticating")
self._sid = await self._auth_manager.login(self)
sys.stderr.write("[dsm] Auth OK\n")
sys.stderr.flush()
self._initialized = True
logger.debug("Lazy init complete")
finally:
self._initializing = False
async def __aenter__(self) -> DsmClient: async def __aenter__(self) -> DsmClient:
logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -235,7 +240,10 @@ class DsmClient:
""" """
sys.stderr.write(f"[dsm] request: {api}/{method}\n") sys.stderr.write(f"[dsm] request: {api}/{method}\n")
sys.stderr.flush() sys.stderr.flush()
await self._ensure_initialized() # Skip init guard if we are already inside _ensure_initialized (e.g. login call).
# The API cache is populated before login, so the cache is ready at this point.
if not self._initializing:
await self._ensure_initialized()
http = self._get_http() http = self._get_http()
if api not in self._api_cache: if api not in self._api_cache: