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

  1. Retry the download — download_attempt is designed for retries and the .part file was flushed, so a fresh attempt resumes/rewrites cleanly.
  2. Check network stability (proxy, VPN, DNS, Wi-Fi) between client and the file host.
  3. Try a different mirror/CDN URL for the same asset if the host supports it.
  4. 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

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.

Related errors


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)