tonhowtf/omniget · info · anyhow::Error
Download cancelled
Error message
Download cancelled
What it means
Thrown in download_streaming when the fetcher's cancel token (self.cancel) is observed to be cancelled, checked at the top of each chunk-read loop iteration. This is the library's cooperative cancellation mechanism: it lets a caller abort an in-flight download and returns a distinct, expected error instead of panicking or leaking the task. The .part file is left on disk (resume data may also be retained depending on use_sidecar_resume).
Solutions
- This error signals intentional cancellation — handle it as a control-flow signal, not a failure: match on the message/cancel cause and skip alerting the user.
- If cancellation was unintended, audit who calls .cancel() on the token (timeout wrappers, UI handlers) and fix the trigger.
- If downloads should resume after cancellation, keep the .part file and re-invoke download with resume/sidecar support enabled.
- If you want the error programmatically distinguishable, wrap the call and map this specific message to your own Cancelled error variant.
Example fix
// before
fetcher.download(&mut progress_tx).await?; // treats cancel as a hard error
// after
match fetcher.download(&mut progress_tx).await {
Err(e) if e.to_string().contains("Download cancelled") => {
tracing::info!("download cancelled by user");
}
other => other?,
} Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the token is fresh before starting
if cancel_token.is_cancelled() {
bail!("not starting download: token already cancelled");
} Try / catch
match fetcher.download(&mut tx).await {
Err(e) if e.to_string().contains("Download cancelled") => {
// expected control flow: clean up UI state, keep .part for resume
set_download_state(DownloadState::Cancelled);
}
other => other?,
} Prevention
- Treat this error as a signal, not a failure — never report it to users as a crash.
- Audit all code paths that call .cancel() on the shared token (timeouts, shutdown hooks).
- Keep the .part file and sidecar resume data so cancelled downloads can resume.
- Cancel the token only when the download task is guaranteed to observe it (task still alive).
When it happens
Trigger: A CancellationToken was passed to the fetcher and .cancel() was called (e.g. user pressed a Cancel button, app shutdown, or a supervisor timed the task out) while download_streaming was between chunks.
Common situations: User-initiated cancel in a GUI (Tauri app) mid-download; application shutdown or window close triggering token cancellation; a new download superseding an old one; watchdog cancelling a stalled download.
Related errors
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/9f52073d96e872c7.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/http_fetcher.rs:327
let resp = req.send().await?;
if !resp.status().is_success() {
return Err(anyhow!("HTTP {} downloading {}", resp.status(), self.url));
}
let total = header_content_length(resp.headers()).or_else(|| resp.content_length());
let mut file = tokio::fs::File::create(part_path).await?;
let mut downloaded: u64 = 0;
let mut stream = resp.bytes_stream();
let mut last_emit = std::time::Instant::now();
let mut anchor_bytes = 0u64;
let mut anchor_time = std::time::Instant::now();
let mut speed_ema: f64 = 0.0;
loop {
if let Some(token) = &self.cancel {
if token.is_cancelled() {
return Err(anyhow!("Download cancelled"));
}
}
match tokio::time::timeout(self.config.read_timeout, stream.next()).await {
Ok(Some(Ok(chunk))) => {
file.write_all(&chunk).await?;
downloaded += chunk.len() as u64;
if last_emit.elapsed() >= Duration::from_millis(250) {
let elapsed = anchor_time.elapsed().as_secs_f64();
if elapsed >= 0.3 {
let instant =
(downloaded.saturating_sub(anchor_bytes)) as f64 / elapsed;
speed_ema = if speed_ema > 0.0 {
speed_ema * 0.6 + instant * 0.4
} else {
instant
};
anchor_bytes = downloaded;
anchor_time = std::time::Instant::now();View on GitHub (pinned to 8600b91f42)