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:
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user