tonhowtf/omniget · error
Download timeout — no data received for 30 seconds
Error message
Download timeout — no data received for 30 seconds
What it means
download_single_stream reads the response body via tokio::time::timeout; if the stream yields no chunk within 30 seconds, the future times out (Err branch of the select) and this error is returned after flushing the partial .part file. It exists so a stalled connection does not hang a download forever. The download is aborted mid-stream; the partial file is kept for resume logic in download_attempt.
Solutions
- Retry the download — download_attempt is designed for retries and the .part file was flushed, so a fresh attempt resumes/rewrites cleanly.
- Check network stability (proxy, VPN, DNS, Wi-Fi) between client and the file host.
- Try a different mirror/CDN URL for the same asset if the host supports it.
- If the server legitimately pauses >30s (e.g. very slow rate limiting), increase the 30s timeout duration around the chunk read in download_single_stream.
Example fix
// before
Err(_) => {
file.flush()?;
return Err(anyhow!("Download timeout — no data received for 30 seconds"));
}
// after — retry with backoff instead of failing outright
Err(_) => {
file.flush()?;
if attempt < max_retries {
tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
continue; // restart download_attempt
}
return Err(anyhow!("Download timeout — no data received for 30 seconds"));
} Defensive patterns
Strategy: retry
Validate before calling
// Preflight: confirm the URL is reachable before starting a long download
let resp = reqwest::Client::new().head(url).timeout(Duration::from_secs(10)).send().await?;
if !resp.status().is_success() { bail!("host unreachable: {}", resp.status()); } Try / catch
// The partial .part file is flushed before the error is raised, so a retry can restart cleanly
match download_attempt(url, dest).await {
Err(e) if e.to_string().contains("Download timeout") => {
warn!("stalled, retrying");
tokio::time::sleep(Duration::from_secs(3)).await;
download_attempt(url, dest).await // retry with backoff
}
other => other,
} Prevention
- Wrap download attempts in a bounded retry loop with exponential backoff — the code already flushes .part files for this purpose.
- Prefer hosts/mirrors with keep-alive-friendly CDNs; test the URL with a HEAD request first.
- Monitor network path (VPN/proxy) health for long downloads on mobile or unstable links.
- Surface the timeout to the user with a 'resume/retry' affordance rather than a dead end.
When it happens
Trigger: Calling download_attempt/download_single_stream when the TCP connection to the server stalls — no body chunk arrives for 30 consecutive seconds (select returns Err from the timeout around next_chunk).
Common situations: Server throttles or stalls mid-transfer; proxy or VPN drops packets without closing the socket; mobile/network switch kills the connection silently; overloaded CDN edge; server keeps connection open but stops sending (dead keepalive).
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- download de {} falhou: HTTP {}
- nao foi possivel buscar {}: {}
- Download failed after {} attempts
- HTTP {} downloading {}
- read timed out after {:?}
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/8fb69b7a87ea3890.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/direct_downloader.rs:598
.send(ProgressUpdate::rich(
percent,
Some(downloaded),
total_size.filter(|t| *t > 0),
speed,
eta,
))
.await;
last_emit = std::time::Instant::now();
}
}
Ok(Some(Err(e))) => {
file.flush()?;
return Err(anyhow!("Download stream error: {}", e));
}
Ok(None) => break,
Err(_) => {
file.flush()?;
return Err(anyhow!(
"Download timeout — no data received for 30 seconds"
));
}
}
}
file.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part_path_appends_suffix() {
let output = Path::new("video.mp4");
let part = part_path_for(output);View on GitHub (pinned to 8600b91f42)