Harden DsmClient against a slow/stalled NAS so no tool can hang
A second, independent failure mode behind the intermittent "MCP not
responding" reports: every tool shares one HTTP client with generous
timeouts (FileStation download/upload used 60s) and no retry. When DSM
briefly stalled (FileStation busy, NAS under load) a single call blocked
for up to a minute, and follow-up calls - even read-only ones like
read_compose - stalled with it, so the whole MCP appeared dead. The path
to the NAS is direct LAN (dsm.gecheckt.de -> 192.168.0.2), so this is DSM
responsiveness, not a network/NAT-loopback issue.
Route every DSM round-trip through a new DsmClient._send that adds:
- a hard wall-clock ceiling (asyncio.wait_for, HARD_CALL_TIMEOUT=25s) so a
call returns even if DSM accepts a request but never responds;
- a single retry on a fresh connection for transient transport failures -
always for connection-establishment errors (request provably never
reached DSM, safe even for POST), and for GETs also on read/protocol
errors (also absorbs the keepalive race where DSM drops an idle conn);
- a clean SynologyError ("NAS did not respond, it may be busy") instead of
a raw transport traceback or a long silent hang.
Also tighten timeouts: default read 30s->15s, FileStation 60s->20s,
build_stream per-read 60s->20s. build_stream stays out of _send (it
streams and manages its own budget).
- add 6 tests for retry/no-retry/ceiling behaviour (320 pass)
- update CLAUDE.md and CHANGELOG; bump 0.8.0 -> 0.9.0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,35 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.9.0] - 2026-06-16
|
||||
|
||||
### Changed (resilience against a slow/stalled NAS)
|
||||
|
||||
Hardened `DsmClient` so a single slow or stalled DSM response can no longer
|
||||
hang a tool — including read-only ones like `read_compose`. Previously every
|
||||
tool shared one HTTP client with generous timeouts (FileStation download/upload
|
||||
used 60 s) and no retry, so when DSM briefly stalled (FileStation busy, NAS
|
||||
under load) a call blocked for up to a minute and follow-up calls stalled with
|
||||
it, making the whole MCP appear dead.
|
||||
|
||||
- **Hard per-call ceiling** — every DSM round-trip is wrapped in
|
||||
`asyncio.wait_for(…, HARD_CALL_TIMEOUT=25 s)`, a backstop that guarantees a
|
||||
call returns even if DSM accepts a request but never responds.
|
||||
- **Tighter timeouts** — default read timeout 30 s → 15 s; FileStation
|
||||
download/upload 60 s → 20 s; `build_stream` per-read 60 s → 20 s.
|
||||
- **Single retry on a fresh connection** for transient transport failures: any
|
||||
connection-establishment error is retried (the request provably never reached
|
||||
DSM, so it is safe even for non-idempotent POSTs), and read/protocol errors
|
||||
are additionally retried for idempotent GETs. This also absorbs the keepalive
|
||||
race where DSM closes an idle connection just as a request is sent.
|
||||
- **Clean error instead of a raw exception** — when both attempts fail, tools
|
||||
now return a `SynologyError` "NAS … did not respond, it may be busy — please
|
||||
retry shortly" instead of a raw transport traceback or a long silent hang.
|
||||
|
||||
Note: each tool may still issue several DSM calls, so a sustained NAS outage is
|
||||
bounded per-call (~15–25 s) rather than per-tool; a per-tool ceiling is a
|
||||
possible future addition.
|
||||
|
||||
## [0.8.0] - 2026-06-16
|
||||
|
||||
### Changed (fixes intermittent "MCP not responding" timeouts)
|
||||
|
||||
Reference in New Issue
Block a user