From e13324e10ca09966c6f0e880201e4bd4a2e4aa9a Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Mon, 13 Apr 2026 15:49:26 +0200 Subject: [PATCH] Fix deadlock in lazy init: _ensure_initialized calling login via request() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/mcp_synology_container/dsm_client.py | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/mcp_synology_container/dsm_client.py b/src/mcp_synology_container/dsm_client.py index 3d593d3..d2abdf3 100644 --- a/src/mcp_synology_container/dsm_client.py +++ b/src/mcp_synology_container/dsm_client.py @@ -101,6 +101,7 @@ class DsmClient: self._reauth_lock = asyncio.Lock() self._init_lock = asyncio.Lock() self._initialized = False + self._initializing = False # True while inside _ensure_initialized logger.debug( "DsmClient: base_url=%s verify_ssl=%s timeout=%d", self._base_url, @@ -131,21 +132,25 @@ class DsmClient: async with self._init_lock: if self._initialized: # re-check inside lock return - sys.stderr.write(f"[dsm] Connecting to {self._base_url}...\n") - sys.stderr.flush() - logger.debug("Lazy init: querying API info from %s", self._base_url) - 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") + self._initializing = True + try: + sys.stderr.write(f"[dsm] Connecting to {self._base_url}...\n") sys.stderr.flush() - logger.debug("Lazy init: authenticating") - self._sid = await self._auth_manager.login(self) - sys.stderr.write("[dsm] Auth OK\n") + logger.debug("Lazy init: querying API info from %s", self._base_url) + await self.query_api_info() + sys.stderr.write(f"[dsm] API info OK ({len(self._api_cache)} APIs)\n") sys.stderr.flush() - self._initialized = True - logger.debug("Lazy init complete") + if self._auth_manager: + 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: logging.getLogger("httpx").setLevel(logging.WARNING) @@ -235,7 +240,10 @@ class DsmClient: """ sys.stderr.write(f"[dsm] request: {api}/{method}\n") 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() if api not in self._api_cache: