fix: v0.2.8 — init cooldown to prevent repeated failed-login hammering
Cache failed `_ensure_initialized` outcomes for 60 seconds so that repeated tool calls during a credential outage (wrong password, IP-blocked 407, DNS failure) don't keep hammering DSM. Each caller gets the same exception raised from the cache until the cooldown window expires, after which a fresh attempt is made. - Adds INIT_ERROR_COOLDOWN module constant (60.0 s). - Adds self._init_error / self._init_error_until state on DsmClient. - Re-raises cached error inside the init lock, emits warning log on cache entry. Addresses M4 from the 0.2.7 review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcp-synology-container"
|
name = "mcp-synology-container"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
description = "MCP server for Synology Container Manager"
|
description = "MCP server for Synology Container Manager"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ logger = logging.getLogger(__name__)
|
|||||||
# Session error codes that trigger transparent re-auth
|
# Session error codes that trigger transparent re-auth
|
||||||
_SESSION_ERROR_CODES = frozenset({106, 107, 119})
|
_SESSION_ERROR_CODES = frozenset({106, 107, 119})
|
||||||
|
|
||||||
|
# Cooldown after a failed init so repeated tool calls don't hammer DSM
|
||||||
|
# (and don't escalate 407 "IP blocked" lockouts).
|
||||||
|
INIT_ERROR_COOLDOWN = 60.0
|
||||||
|
|
||||||
# Parameters to mask in debug logging
|
# Parameters to mask in debug logging
|
||||||
_SENSITIVE_PARAMS = frozenset({"passwd", "_sid", "device_id", "otp_code", "device_token"})
|
_SENSITIVE_PARAMS = frozenset({"passwd", "_sid", "device_id", "otp_code", "device_token"})
|
||||||
|
|
||||||
@@ -111,6 +115,8 @@ class DsmClient:
|
|||||||
self._init_lock = asyncio.Lock()
|
self._init_lock = asyncio.Lock()
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._initializing = False # True while inside _ensure_initialized
|
self._initializing = False # True while inside _ensure_initialized
|
||||||
|
self._init_error: Exception | None = None
|
||||||
|
self._init_error_until: float = 0.0
|
||||||
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,
|
||||||
@@ -141,6 +147,10 @@ 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
|
||||||
|
# Negative cache: re-raise cached error during cooldown window.
|
||||||
|
now = asyncio.get_event_loop().time()
|
||||||
|
if self._init_error is not None and now < self._init_error_until:
|
||||||
|
raise self._init_error
|
||||||
self._initializing = True
|
self._initializing = True
|
||||||
try:
|
try:
|
||||||
sys.stderr.write(f"[dsm] Connecting to {self._base_url}...\n")
|
sys.stderr.write(f"[dsm] Connecting to {self._base_url}...\n")
|
||||||
@@ -157,7 +167,18 @@ class DsmClient:
|
|||||||
sys.stderr.write("[dsm] Auth OK\n")
|
sys.stderr.write("[dsm] Auth OK\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
self._init_error = None
|
||||||
|
self._init_error_until = 0.0
|
||||||
logger.debug("Lazy init complete")
|
logger.debug("Lazy init complete")
|
||||||
|
except Exception as e:
|
||||||
|
self._init_error = e
|
||||||
|
self._init_error_until = now + INIT_ERROR_COOLDOWN
|
||||||
|
logger.warning(
|
||||||
|
"Lazy init failed (%s); will retry after %.0fs cooldown",
|
||||||
|
type(e).__name__,
|
||||||
|
INIT_ERROR_COOLDOWN,
|
||||||
|
)
|
||||||
|
raise
|
||||||
finally:
|
finally:
|
||||||
self._initializing = False
|
self._initializing = False
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcp-synology-container"
|
name = "mcp-synology-container"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
|||||||
Reference in New Issue
Block a user