{"record":{"id":"5f0f51e0f4f550f2","repo":"tonhowtf/omniget","slug":"read-timed-out-after","errorCode":null,"errorMessage":"read timed out after {:?}","messagePattern":"read timed out after (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/http_fetcher.rs","lineNumber":378,"sourceCode":"                                None,\n                            ),\n                        };\n                        let _ = progress_tx\n                            .send(ProgressUpdate::rich(\n                                pct,\n                                Some(downloaded),\n                                total.filter(|t| *t > 0),\n                                speed,\n                                eta,\n                            ))\n                            .await;\n                        last_emit = std::time::Instant::now();\n                    }\n                }\n                Ok(Some(Err(e))) => return Err(anyhow!(\"stream error: {}\", e)),\n                Ok(None) => break,\n                Err(_) => {\n                    return Err(anyhow!(\n                        \"read timed out after {:?}\",\n                        self.config.read_timeout\n                    ))\n                }\n            }\n        }\n\n        file.flush().await?;\n        drop(file);\n        tokio::fs::rename(part_path, &self.output_path).await?;\n        let _ = progress_tx.send(ProgressUpdate::percent(100.0)).await;\n        Ok(HttpFetcherResult {\n            bytes_written: downloaded,\n        })\n    }\n\n    async fn download_chunked(\n        &self,","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/http_fetcher.rs#L360-L396","documentation":"Thrown in download_streaming when tokio::time::timeout(self.config.read_timeout, stream.next()) elapses — i.e. no new chunk arrived within the configured read_timeout duration. This is an inactivity timeout per chunk read, not a total download time limit; a download is only killed if the stream stalls completely for read_timeout. The .part file keeps data received so far.","triggerScenarios":"The remote server or an intermediary stops sending data for longer than self.config.read_timeout while the connection stays open: stalled transfer, server-side hang, dead NAT connection, or a read_timeout configured too aggressively for a slow/throttled source.","commonSituations":"read_timeout set to a few seconds while downloading from a slow/throttled mirror; server pauses to handle load; laptop resumed from sleep with a half-dead TCP connection; mobile network handoff stalling the stream; VPN tunnel momentarily freezing.","solutions":["Increase config.read_timeout to a value comfortably above the worst-case inter-chunk delay (e.g. 30-60s for slow mirrors).","Retry the download with resume so the .part file is continued from where the stall occurred.","Add an automatic retry wrapper around download for timeout errors with exponential backoff.","Check whether the source server/CDN is throttling or stalling; try an alternate mirror or HTTP/1.1 instead of HTTP/2.","Keep TCP keepalives enabled on the client (reqwest/hyper defaults) so dead connections error out quickly instead of silently stalling."],"exampleFix":"// before\nlet config = FetcherConfig { read_timeout: Duration::from_secs(3), ..Default::default() };\n\n// after\nlet config = FetcherConfig { read_timeout: Duration::from_secs(60), ..Default::default() };","handlingStrategy":"retry","validationCode":"// don't start with an unreasonably small read_timeout\nif config.read_timeout < Duration::from_secs(15) {\n    bail!(\"read_timeout {:?} too small for downloads\", config.read_timeout);\n}","typeGuard":null,"tryCatchPattern":"match fetcher.download(&mut tx).await {\n    Err(e) if e.to_string().contains(\"read timed out\") => {\n        // stall: resume download with backoff, escalate timeout per attempt\n        for attempt in 0..3 {\n            tokio::time::sleep(Duration::from_secs(1 << attempt)).await;\n            if fetcher.download_resuming(&mut tx).await.is_ok() { break; }\n        }\n    }\n    other => other?,\n}","preventionTips":["Set read_timeout generously (30-60s) for downloads from slow or throttled mirrors.","Remember it's a per-chunk inactivity timeout, not a total-duration cap — size it for worst-case stalls.","Always combine with resume support so a timeout doesn't discard prior progress.","Watch for stalls right after system sleep/resume or network handoffs; reconnect proactively."],"tags":["timeout","network","streaming","download"],"backgroundTag":"request-timeout","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}