From 0c2638fec3e0a249804c75a0bbc4a5596a7243c3 Mon Sep 17 00:00:00 2001 From: "jonas h." Date: Tue, 30 Jun 2026 17:36:39 +0200 Subject: [PATCH] fix init angain again again (hopefully last time) --- cli/__pycache__/sjnmusiccpython-314.pyc | Bin 0 -> 26640 bytes cli/sjnmusic | 11 ++++-- daemon/src/server.rs | 10 ++++- daemon/src/state.rs | 11 +++++- gui-rs/src/daemon.rs | 48 +++++++++++++++++++----- 5 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 cli/__pycache__/sjnmusiccpython-314.pyc diff --git a/cli/__pycache__/sjnmusiccpython-314.pyc b/cli/__pycache__/sjnmusiccpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6944f9bff66cb1f3c91de642f1693dd951846a7e GIT binary patch literal 26640 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 literal 0 HcmV?d00001 diff --git a/cli/sjnmusic b/cli/sjnmusic index 50df4a9..6f7a31c 100755 --- a/cli/sjnmusic +++ b/cli/sjnmusic @@ -139,7 +139,7 @@ def join_query(args) -> str: return " ".join(parts).strip() -def call(host, port, method, route, body=None, query=None): +def call(host, port, method, route, body=None, query=None, timeout=15): qs = "" if query: qs = "?" + urllib.parse.urlencode(query) @@ -153,7 +153,7 @@ def call(host, port, method, route, body=None, query=None): req = urllib.request.Request(url=url, data=data, headers=headers, method=method) try: - with urllib.request.urlopen(req, timeout=15) as resp: + with urllib.request.urlopen(req, timeout=timeout) as resp: payload = resp.read().decode("utf-8") return resp.status, payload except urllib.error.HTTPError as e: @@ -333,8 +333,13 @@ def main(argv=None): query = {"q": q} if limit is not None and limit > 0: query["limit"] = str(limit) + # yt-dlp searches can easily take 30+ seconds on a slow network + # (the daemon calls `ytsearchN:QUERY -j` which hits YouTube). + # The default 15 s `urllib` timeout short-circuits the picker + # before the daemon even returns, so give this one endpoint a + # generous budget. All other endpoints keep the 15 s default. status, payload = call( - host, port, "GET", "/search/yt", query=query + host, port, "GET", "/search/yt", query=query, timeout=120 ) obj = try_parse(payload) results = (obj or {}).get("results") or [] diff --git a/daemon/src/server.rs b/daemon/src/server.rs index fc7a9b2..3a6c5c3 100644 --- a/daemon/src/server.rs +++ b/daemon/src/server.rs @@ -180,7 +180,15 @@ fn handle( .get("limit") .and_then(|s| s.parse::().ok()) .unwrap_or(state.search_count); - match state.search_yt_sync(&q, limit) { + // Release the synchronous state guard BEFORE calling + // search_yt_sync — yt-dlp can take 30+ seconds on a slow + // network and holding the daemon mutex here would freeze + // every other endpoint (including the GUI's `/init`, + // `/downloads`, `/now-playing` polls) for the entire + // duration. Mirrors the drop-then-call pattern that + // `init_sync` already uses. + drop(state); + match DaemonState::search_yt_sync(&q, limit) { Ok(results) => json_response(200, &serde_json::json!({ "query": q, "limit": limit, diff --git a/daemon/src/state.rs b/daemon/src/state.rs index 2a3c540..74f02e7 100644 --- a/daemon/src/state.rs +++ b/daemon/src/state.rs @@ -556,7 +556,16 @@ impl DaemonState { /// non-zero exit are tolerated as much as possible: an empty list is /// returned when nothing parsed, while a yt-dlp startup failure surfaces /// a descriptive error string so the CLI can show it to the user. - pub fn search_yt_sync(&self, query: &str, limit: usize) -> Result, String> { + /// + /// Lives as an associated function (no `&self`) so the HTTP handler can + /// release the daemon state mutex before spawning yt-dlp. Earlier + /// revisions invoked this as `state.search_yt_sync(...)` *while* + /// holding the single-threaded `tiny_http` request handler's state + /// guard, which froze the entire daemon (including `/init`, `/queue`, + /// `/now-playing`) for the full duration of a yt-dlp search — typically + /// 30+ seconds on a slow network. Mirrors the lock-free pattern already + /// used for `init_sync` below. + pub fn search_yt_sync(query: &str, limit: usize) -> Result, String> { let limit = limit.max(1); let src = format!("ytsearch{limit}:{query}"); debug!("yt-dlp search: {src}"); diff --git a/gui-rs/src/daemon.rs b/gui-rs/src/daemon.rs index 5a1e7c0..b5977e3 100644 --- a/gui-rs/src/daemon.rs +++ b/gui-rs/src/daemon.rs @@ -330,12 +330,23 @@ impl DaemonClient { } pub fn search_yt(&self, q: &str, limit: usize) -> Result, DaemonError> { + // `/search/yt` shells out to `yt-dlp ytsearchN:...` on the daemon + // side, which can easily take 30+ seconds on a slow network — well + // past the agent's 8 s default. Override per-request so the picker + // doesn't ghost-fail with a transport timeout. All other + // endpoints keep the default; the daemon's state lock is no + // longer held during the search (see daemon state.rs), so other + // GETs (now-playing, downloads, queue) keep flowing while a + // search is in flight. Ok(self - .get::(&format!( - "/search/yt?q={}&limit={}", - urlencoding::encode(q), - limit - ))? + .get_with_timeout::( + &format!( + "/search/yt?q={}&limit={}", + urlencoding::encode(q), + limit + ), + Duration::from_secs(120), + )? .results) } @@ -484,7 +495,19 @@ impl DaemonClient { // ---------------------------------------------------------------- fn get Deserialize<'de>>(&self, path: &str) -> Result { - self._request_json("GET", path, None) + self._request_json("GET", path, None, None) + } + + /// Like `get` but lets callers override the per-request timeout. Used + /// only for endpoints that back onto slow subprocess calls (currently + /// just `/search/yt`, since yt-dlp's first network round-trip can blow + /// past the 8 s agent default). + fn get_with_timeout Deserialize<'de>>( + &self, + path: &str, + timeout: Duration, + ) -> Result { + self._request_json("GET", path, None, Some(timeout)) } fn post_json Deserialize<'de>>( @@ -492,7 +515,7 @@ impl DaemonClient { path: &str, body: &serde_json::Value, ) -> Result { - self._request_json("POST", path, Some(body)) + self._request_json("POST", path, Some(body), None) } fn patch Deserialize<'de>>( @@ -500,7 +523,7 @@ impl DaemonClient { path: &str, body: &serde_json::Value, ) -> Result { - self._request_json("PATCH", path, Some(body)) + self._request_json("PATCH", path, Some(body), None) } fn del_json Deserialize<'de>>( @@ -508,11 +531,11 @@ impl DaemonClient { path: &str, body: &serde_json::Value, ) -> Result { - self._request_json("DELETE", path, Some(body)) + self._request_json("DELETE", path, Some(body), None) } fn del Deserialize<'de>>(&self, path: &str) -> Result { - self._request_json("DELETE", path, None) + self._request_json("DELETE", path, None, None) } fn _request_json Deserialize<'de>>( @@ -520,6 +543,7 @@ impl DaemonClient { method: &str, path: &str, body: Option<&serde_json::Value>, + timeout: Option, ) -> Result { let url = format!("{}{}", self.base, path); let req = match method { @@ -535,6 +559,10 @@ impl DaemonClient { ))) } }; + let req = match timeout { + Some(t) => req.timeout(t), + None => req, + }; let req = match body { Some(b) => req.send_json(ureq::json!(b.clone())), None => req.call(),