tonhowtf/omniget · error · anyhow::Error

read timed out after {:?}

Error message

read timed out after {:?}

What it means

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.

Solutions

  1. Increase config.read_timeout to a value comfortably above the worst-case inter-chunk delay (e.g. 30-60s for slow mirrors).
  2. Retry the download with resume so the .part file is continued from where the stall occurred.
  3. Add an automatic retry wrapper around download for timeout errors with exponential backoff.
  4. Check whether the source server/CDN is throttling or stalling; try an alternate mirror or HTTP/1.1 instead of HTTP/2.
  5. Keep TCP keepalives enabled on the client (reqwest/hyper defaults) so dead connections error out quickly instead of silently stalling.

Example fix

// before
let config = FetcherConfig { read_timeout: Duration::from_secs(3), ..Default::default() };

// after
let config = FetcherConfig { read_timeout: Duration::from_secs(60), ..Default::default() };
Defensive patterns

Strategy: retry

Validate before calling

// don't start with an unreasonably small read_timeout
if config.read_timeout < Duration::from_secs(15) {
    bail!("read_timeout {:?} too small for downloads", config.read_timeout);
}

Try / catch

match fetcher.download(&mut tx).await {
    Err(e) if e.to_string().contains("read timed out") => {
        // stall: resume download with backoff, escalate timeout per attempt
        for attempt in 0..3 {
            tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
            if fetcher.download_resuming(&mut tx).await.is_ok() { break; }
        }
    }
    other => other?,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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/5f0f51e0f4f550f2. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/http_fetcher.rs:378

                                None,
                            ),
                        };
                        let _ = progress_tx
                            .send(ProgressUpdate::rich(
                                pct,
                                Some(downloaded),
                                total.filter(|t| *t > 0),
                                speed,
                                eta,
                            ))
                            .await;
                        last_emit = std::time::Instant::now();
                    }
                }
                Ok(Some(Err(e))) => return Err(anyhow!("stream error: {}", e)),
                Ok(None) => break,
                Err(_) => {
                    return Err(anyhow!(
                        "read timed out after {:?}",
                        self.config.read_timeout
                    ))
                }
            }
        }

        file.flush().await?;
        drop(file);
        tokio::fs::rename(part_path, &self.output_path).await?;
        let _ = progress_tx.send(ProgressUpdate::percent(100.0)).await;
        Ok(HttpFetcherResult {
            bytes_written: downloaded,
        })
    }

    async fn download_chunked(
        &self,

View on GitHub (pinned to 8600b91f42)