From 38d818ab31da4e661502032882837cc3a8438fea Mon Sep 17 00:00:00 2001 From: LAP87 Date: Sat, 19 Sep 2026 01:53:23 +0200 Subject: [PATCH] Revert "decypharr: retry the WebDAV readability probe once with a longer timeout on cold files" In production the retry cost 240 x 12.5 s per hourly scan (link step 367 s -> 2,274 s) and rescued nothing: files that need more than 2.5 s for their first byte also need more than 10 s. Bump version to 1.1.0-rc.16. Co-Authored-By: Claude Opus 5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- docs/CHANGELOG.md | 8 +++- src/api/decypharr.rs | 97 ++++++++------------------------------------ 4 files changed, 27 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9c68f8..ad4f356 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2107,7 +2107,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symlinkarr" -version = "1.1.0-rc.15" +version = "1.1.0-rc.16" dependencies = [ "anyhow", "askama", diff --git a/Cargo.toml b/Cargo.toml index f0780c1..dc23de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "symlinkarr" -version = "1.1.0-rc.15" +version = "1.1.0-rc.16" edition = "2021" authors = ["Lenny", "Gemini CLI", "Antigravity"] description = "Intelligent symlink manager for Real-Debrid-backed media libraries" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4e20391..eb8135b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,10 +2,16 @@ ## Release Target -- package version for this push: `1.1.0-rc.15` +- package version for this push: `1.1.0-rc.16` - posture: `v1.1 release candidate with Arr backfill, fail-closed acquisition ranking, current dependencies, synchronized Docker release channels, and updated operator documentation` - intended use: local-first host or Docker installs, with Windows 11 users running through WSL2 or a Linux container +## 2026-09-19 - v1.1.0-rc.16 Revert Cold-File Probe Retry + +### Code Changes + +- reverted the rc.14 cold-file probe retry. In production it cost 240 × 12.5 s per hourly scan (link step 367 s → 2,274 s) and rescued nothing: files that need more than 2.5 s for their first byte also need more than 10 s. Folders that fail the probe are skipped for the run exactly as before rc.14. + ## 2026-09-18 - v1.1.0-rc.15 Mount Folder From Original Torrent Name ### Code Changes diff --git a/src/api/decypharr.rs b/src/api/decypharr.rs index 919c92d..ad9bb35 100644 --- a/src/api/decypharr.rs +++ b/src/api/decypharr.rs @@ -246,11 +246,6 @@ impl DecypharrClient { Ok(url) } - /// How much longer the second attempt may take when the first one timed out. A cold - /// debrid file needs a link generated and a CDN warm-up before its first byte (often - /// several seconds); once served it answers in well under a second. - const COLD_PROBE_RETRY_FACTOR: u32 = 4; - pub async fn probe_webdav_path( &self, relative_path: &Path, @@ -260,42 +255,18 @@ impl DecypharrClient { .build_webdav_url(relative_path) .map_err(|err| WebDavProbeError::Unreadable(err.to_string()))?; - let send = |timeout: Duration| { - let mut req = self - .client - .get(url.clone()) - .timeout(timeout) - .header(reqwest::header::RANGE, "bytes=0-0"); - if let Some((key, val)) = self.auth_header() { - req = req.header(key, val); - } - req.send() - }; + let mut req = self + .client + .get(url) + .timeout(timeout) + .header(reqwest::header::RANGE, "bytes=0-0"); + if let Some((key, val)) = self.auth_header() { + req = req.header(key, val); + } - let resp = match send(timeout).await { - Ok(resp) => resp, - Err(err) if err.is_timeout() => { - let retry_timeout = timeout * Self::COLD_PROBE_RETRY_FACTOR; - debug!( - "WebDAV probe of {} timed out after {:?}; retrying once with {:?} (cold file?)", - relative_path.display(), - timeout, - retry_timeout - ); - send(retry_timeout).await.map_err(|err| { - WebDavProbeError::Unreadable(format!( - "webdav probe transport error after cold retry ({:?}): {}", - retry_timeout, err - )) - })? - } - Err(err) => { - return Err(WebDavProbeError::Unreadable(format!( - "webdav probe transport error: {}", - err - ))) - } - }; + let resp = req.send().await.map_err(|err| { + WebDavProbeError::Unreadable(format!("webdav probe transport error: {}", err)) + })?; match resp.status() { StatusCode::OK | StatusCode::PARTIAL_CONTENT => Ok(()), @@ -699,51 +670,19 @@ mod tests { assert!(request.to_ascii_lowercase().contains("range: bytes=0-0")); } - #[tokio::test] - async fn probe_webdav_path_retries_once_with_a_longer_timeout_for_cold_files() { - // First byte arrives after 120 ms: too slow for the 50 ms probe, fine for the - // 200 ms cold retry. - let listener = TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = listener.local_addr().unwrap(); - thread::spawn(move || { - for _ in 0..2 { - if let Ok((mut stream, _)) = listener.accept() { - let mut req_buf = [0u8; 1024]; - let _ = stream.read(&mut req_buf); - thread::sleep(Duration::from_millis(120)); - let _ = stream.write_all( - b"HTTP/1.1 206 Partial Content\r\nContent-Length: 1\r\nConnection: close\r\n\r\nx", - ); - } - } - }); - - let client = DecypharrClient::new(&format!("http://{}", addr), None); - client - .probe_webdav_path( - Path::new("__all__/cold/file.mkv"), - Duration::from_millis(50), - ) - .await - .unwrap(); - } - #[tokio::test] async fn probe_webdav_path_times_out_when_server_stalls() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); thread::spawn(move || { - // Stall both the probe and its cold retry. - for _ in 0..2 { - if let Ok((mut stream, _)) = listener.accept() { - let mut req_buf = [0u8; 1024]; - let _ = stream.read(&mut req_buf); - thread::sleep(Duration::from_millis(400)); - let _ = stream.write_all( - b"HTTP/1.1 206 Partial Content\r\nContent-Length: 1\r\nConnection: close\r\n\r\nx", - ); - } + if let Ok((mut stream, _)) = listener.accept() { + let mut req_buf = [0u8; 1024]; + let _ = stream.read(&mut req_buf); + thread::sleep(Duration::from_millis(200)); + let _ = stream.write_all( + b"HTTP/1.1 206 Partial Content\r\nContent-Length: 1\r\nConnection: close\r\n\r\nx", + ); } });