{"record":{"id":"9fdedff0bbe253d0","repo":"tonhowtf/omniget","slug":"http-downloading-segment","errorCode":null,"errorMessage":"HTTP {} downloading segment","messagePattern":"HTTP (.+?) downloading segment","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/hls_downloader.rs","lineNumber":730,"sourceCode":"    let mut last_err = None;\n    for attempt in 0..max_retries {\n        if cancel.is_cancelled() {\n            anyhow::bail!(\"Download cancelled\");\n        }\n\n        let result = tokio::time::timeout(SEGMENT_TIMEOUT, async {\n            let resp = apply_referer_headers(client.get(url), referer)\n                .header(\"User-Agent\", user_agent)\n                .send()\n                .await?;\n\n            let status = resp.status();\n            if !status.is_success() {\n                let code = status.as_u16();\n                if (400..500).contains(&code) && code != 429 && code != 408 {\n                    return Err(anyhow::anyhow!(\"HTTP {} (fatal) downloading segment\", code));\n                }\n                return Err(anyhow::anyhow!(\"HTTP {} downloading segment\", code));\n            }\n\n            resp.bytes()\n                .await\n                .map(|b| b.to_vec())\n                .map_err(|e| anyhow::anyhow!(e))\n        })\n        .await;\n\n        match result {\n            Ok(Ok(data)) => return Ok(data),\n            Ok(Err(e)) => {\n                if e.to_string().contains(\"(fatal)\") {\n                    return Err(e);\n                }\n                last_err = Some(e);\n            }\n            Err(_) => last_err = Some(anyhow::anyhow!(\"Timeout downloading segment\")),","sourceCodeStart":712,"sourceCodeEnd":748,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/hls_downloader.rs#L712-L748","documentation":"Raised in download_segment_with_retry when the segment response status is a non-success that is NOT treated as fatal: any 5xx server error, 429 rate limit, or 408 request timeout. Unlike the fatal branch, the caller's retry loop keeps retrying this segment with exponential backoff. The status code is included in the message.","triggerScenarios":"Segment GET returns 500/502/503/504 from the CDN or origin, or 429 Too Many Requests / 408 Request Timeout. After exhausting retries the loop surfaces the last such error.","commonSituations":"CDN under load (502/503 from CloudFront/Akamai edge), server-side rate limiting (429) when downloading many parallel segments, origin timeouts (408/504) on slow storage backends, transient upstream failures during live events.","solutions":["Let the built-in retry with jittered backoff handle it; if it still fails, lower concurrent segment downloads to reduce 429s.","Respect Retry-After headers on 429/503 responses when backing off.","Retry the whole download later — 5xx are typically transient server-side issues.","Use a different CDN mirror/edge or fallback rendition URL if the provider offers one."],"exampleFix":"// before\nreturn Err(anyhow::anyhow!(\"HTTP {} downloading segment\", code));\n// after\nif code == 429 {\n    if let Some(ra) = resp.headers().get(reqwest::header::RETRY_AFTER).and_then(|v| v.to_str().ok()) {\n        let secs: u64 = ra.parse().unwrap_or(2);\n        tokio::time::sleep(std::time::Duration::from_secs(secs)).await;\n    }\n}\nreturn Err(anyhow::anyhow!(\"HTTP {} downloading segment {} (retryable)\", code, seg_url));","handlingStrategy":"retry","validationCode":"// classify status before deciding fate\nlet code = resp.status().as_u16();\nlet retryable = code >= 500 || code == 429 || code == 408;\nif retryable {\n    if let Some(retry_after) = resp.headers().get(\"Retry-After\").and_then(|v| v.to_str().ok()) {\n        let secs: u64 = retry_after.parse().unwrap_or(1);\n        tokio::time::sleep(std::time::Duration::from_secs(secs)).await;\n    }\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if !e.to_string().contains(\"(fatal)\") => {\n        // retryable: keep last_err and back off with jitter\n        last_err = Some(e);\n        tokio::time::sleep(jittered_backoff(attempt)).await;\n    }\n    Err(e) => return Err(e),\n    Ok(v) => return Ok(v),\n}","preventionTips":["Cap concurrent segment downloads to avoid triggering 429 rate limits.","Honor Retry-After headers instead of fixed backoff on 429/503.","Use jittered exponential backoff so parallel workers don't hammer the CDN in lockstep.","Configure a reasonable total time budget so transient 5xx storms don't stall the whole download."],"tags":["http","hls","network","retryable"],"backgroundTag":"http-error-response","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"}