{"record":{"id":"8a92e06d00cb3261","repo":"tonhowtf/omniget","slug":"stream-error","errorCode":null,"errorMessage":"stream error: {}","messagePattern":"stream error: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/http_fetcher.rs","lineNumber":375,"sourceCode":"                            None => (\n                                ((downloaded as f64 / (downloaded as f64 + 500_000.0)) * 100.0)\n                                    .min(95.0),\n                                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    }","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/http_fetcher.rs#L357-L393","documentation":"Thrown in download_streaming when the response body stream itself yields an error (Ok(Some(Err(e))) from the bytes_stream). This means the HTTP connection broke mid-download — the transport or hyper layer produced an I/O error while reading a chunk — as opposed to a timeout (handled separately) or a bad status code. The .part file retains everything downloaded before the failure and can be used for resume.","triggerScenarios":"resp.bytes_stream() returns an Err item during the loop: connection reset by peer (RST), TLS handshake renegotiation failure, premature EOF (server closed connection without completing Content-Length), HTTP/2 GOAWAY, or proxy dropped the connection mid-transfer.","commonSituations":"Flaky Wi-Fi/mobile network or VPN drop mid-download; server or load balancer idle-killing long transfers; CDN edge node restarting; corporate proxy terminating long-lived connections; HTTP/2 stream reset by the server.","solutions":["Retry the download with resume enabled so the .part file is continued rather than restarted (Range request / sidecar resume).","Wrap the download call in a retry loop with exponential backoff for transient network errors.","Reduce exposure to idle-connection kills by lowering chunk read time or verifying the server's keep-alive/timeout settings.","Inspect the wrapped io/hyper error (the {} in the message) for the precise cause (e.g. ConnectionReset, UnexpectedEof) and address it specifically.","Test with a wired/stable network or different DNS/VPN to rule out local network instability."],"exampleFix":"// before\nfetcher.download(&mut progress_tx).await?; // one shot, no resume\n\n// after\nfor attempt in 0..5 {\n    match fetcher.download(&mut progress_tx).await {\n        Ok(()) => break,\n        Err(e) if attempt < 4 && is_transient_stream_error(&e) => {\n            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;\n        }\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":"// sanity-check reachability before a long transfer\nlet resp = client.get(&url).send().await?;\nif !resp.status().is_success() {\n    bail!(\"source unreachable: {}\", resp.status());\n}","typeGuard":null,"tryCatchPattern":"match fetcher.download(&mut tx).await {\n    Err(e) if e.to_string().starts_with(\"stream error\") => {\n        // transient mid-stream failure: resume from .part with backoff\n        tokio::time::sleep(Duration::from_secs(2)).await;\n        fetcher.download_resuming(&mut tx).await?;\n    }\n    other => other?,\n}","preventionTips":["Enable resume (.part continuation) so stream breaks don't restart from zero.","Retry transient stream errors with exponential backoff (3-5 attempts).","Prefer stable wired networks for very large transfers; avoid VPN/proxy hops when possible.","Check the wrapped cause (ConnectionReset, UnexpectedEof, etc.) to pick the right remedy."],"tags":["network","streaming","download","connection-reset"],"backgroundTag":"network-request-failed","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"}