Stop the admin panel hanging when slskd is down
/api/admin/system probed slskd with the shared client, whose 30s timeout is sized for searches and transfers. So the panel sat on skeletons for the full 30s exactly when slskd was unreachable -- the case you open it to diagnose -- and the 5s UI poll stacked requests behind it. Probes now get 4s: above a LAN round-trip, under the poll interval. httpx.ConnectTimeout also stringifies to "", so the banner rendered "slskd unreachable -- " with nothing after the dash. Fall back to the exception class name. Add dev.sh, because the documented dev commands could not work: MUSE_DB defaults to /config/muse.db which a Mac cannot write, and the auth preflight refuses to boot without a client ID. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f3c64d4fd0
commit
6ef580780d
4 files changed
+90
-12
No files matched your search
@@ -434,14 +434,32 @@ def set_state(job_id: str, state: str, detail: str = "", **cols):
|
||||
|
||||
# ─────────────────────────── slskd ───────────────────────────
|
||||
|
||||
def slskd_client() -> httpx.AsyncClient:
|
||||
def slskd_client(timeout: float = 30.0) -> httpx.AsyncClient:
|
||||
"""30s suits searches and transfer polling, which are legitimately slow.
|
||||
|
||||
Status probes pass something much shorter: they run on a UI poll, and a
|
||||
probe that outlasts its own poll interval stacks requests and leaves the
|
||||
admin panel on skeletons for the whole timeout — precisely when slskd is
|
||||
down and you opened the panel to find out why.
|
||||
"""
|
||||
return httpx.AsyncClient(
|
||||
base_url=SLSKD_URL,
|
||||
headers={"X-API-Key": SLSKD_API_KEY},
|
||||
timeout=httpx.Timeout(30.0),
|
||||
timeout=httpx.Timeout(timeout),
|
||||
)
|
||||
|
||||
|
||||
# slskd is on the LAN, so a reachable instance answers in milliseconds. Long
|
||||
# enough to absorb a slow moment, short enough to stay under the 5s UI poll.
|
||||
PROBE_TIMEOUT = 4.0
|
||||
|
||||
|
||||
def why(e: Exception) -> str:
|
||||
"""httpx's timeout and connection errors often str() to an empty string,
|
||||
which would render as 'slskd unreachable — ' with nothing after it."""
|
||||
return (str(e) or type(e).__name__)[:120]
|
||||
|
||||
|
||||
def ext_of(remote_path: str) -> str:
|
||||
# Soulseek paths are Windows-style; the API's `extension` field comes back
|
||||
# empty in practice, so derive it ourselves.
|
||||
@@ -1752,7 +1770,7 @@ async def admin_system(request: Request):
|
||||
|
||||
slskd: dict = {"ok": False}
|
||||
try:
|
||||
async with slskd_client() as sc:
|
||||
async with slskd_client(PROBE_TIMEOUT) as sc:
|
||||
r = await sc.get("/api/v0/application")
|
||||
slskd["ok"] = r.status_code == 200
|
||||
if r.status_code == 200:
|
||||
@@ -1767,7 +1785,7 @@ async def admin_system(request: Request):
|
||||
else:
|
||||
slskd["error"] = f"HTTP {r.status_code}"
|
||||
except Exception as e:
|
||||
slskd["error"] = str(e)[:120]
|
||||
slskd["error"] = why(e)
|
||||
out["slskd"] = slskd
|
||||
return out
|
||||
|
||||
@@ -1775,13 +1793,13 @@ async def admin_system(request: Request):
|
||||
@app.get("/api/health")
|
||||
async def health():
|
||||
ok, detail = True, {}
|
||||
async with slskd_client() as sc:
|
||||
async with slskd_client(PROBE_TIMEOUT) as sc:
|
||||
try:
|
||||
r = await sc.get("/api/v0/application")
|
||||
detail["slskd"] = r.status_code
|
||||
ok = ok and r.status_code == 200
|
||||
except Exception as e:
|
||||
detail["slskd"] = str(e)
|
||||
detail["slskd"] = why(e)
|
||||
ok = False
|
||||
detail["library_writable"] = os.access(LIBRARY_DIR, os.W_OK)
|
||||
detail["downloads_readable"] = os.access(DOWNLOAD_DIR, os.R_OK)
|
||||
|
||||
Reference in new issue
Block a user