seanmonstar/reqwest · critical
timeout
Error message
timeout
What it means
This is a PANIC (not a returned `Error`) raised at wasm/mod.rs:70 by `.expect("timeout")`. The wasm client converts the per-request `Duration` to milliseconds and narrows `u128 → i32` for the browser's `setTimeout`; if the millisecond count exceeds `i32::MAX` (~2,147,483,647 ms ≈ 24.8 days) the narrowing fails and the future's task panics.
Source
Thrown at src/wasm/mod.rs:70
Ok(AbortGuard {
ctrl: AbortController::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?,
timeout: None,
})
}
fn signal(&self) -> AbortSignal {
self.ctrl.signal()
}
fn timeout(&mut self, timeout: Duration) {
let ctrl = self.ctrl.clone();
let abort =
Closure::once(move || ctrl.abort_with_reason(&"reqwest::errors::TimedOut".into()));
let timeout = set_timeout(
abort.as_ref().unchecked_ref::<js_sys::Function>(),
timeout.as_millis().try_into().expect("timeout"),
);
if let Some((id, _)) = self.timeout.replace((timeout, abort)) {
clear_timeout(id);
}
}
}
impl Drop for AbortGuard {
fn drop(&mut self) {
self.ctrl.abort();
if let Some((id, _)) = self.timeout.take() {
clear_timeout(id);
}
}
}
View on GitHub (pinned to 17e9bcb51c)
Solutions
- Cap the per-request timeout well below ~24.8 days (e.g. clamp to a sane max like an hour) before passing it to `.timeout()`.
- Pass `None` (no `.timeout()` call) instead of a giant duration when you mean 'unlimited'.
- If you must have very long deadlines, implement them in app logic rather than via the wasm `setTimeout` path.
Example fix
// before (wasm)
let r = client.get(url).timeout(Duration::MAX).send().await?; // panics 'timeout'
// after
const CAP: Duration = Duration::from_secs(3600);
let req = client.get(url);
let req = match maybe_timeout {
Some(d) if d <= CAP => req.timeout(d),
_ => req, // no timeout
};
let r = req.send().await?; Defensive patterns
Strategy: validation
Validate before calling
const WASM_TIMEOUT_CAP: Duration = Duration::from_secs(3600);
fn clamp_timeout(d: Duration) -> Option<Duration> {
(d <= WASM_TIMEOUT_CAP).then_some(d)
}
// usage on wasm
let req = client.get(url);
let req = maybe_timeout.and_then(clamp_timeout).map(|d| req.timeout(d)).unwrap_or(req);
Prevention
- Never pass Duration::MAX or huge durations to .timeout() in wasm; pass None instead.
- Clamp config-driven timeouts to a sane ceiling (< 1 hour) before reaching reqwest.
- Remember this is a panic, not an Err — it will unwind the wasm task unless caught by a panic hook.
When it happens
Trigger: In a wasm target, calling `RequestBuilder::timeout(Duration::from_secs(N))` (or per-request timeout) where N is large enough that `as_millis() > i32::MAX` — e.g. `Duration::from_secs(u64::MAX)`, a 30-day timeout, or accidentally passing `Duration::MAX`.
Common situations: Loading timeout from config as a raw number of seconds and overflowing; using `Duration::MAX` as 'no timeout'; copy-paste producing an absurd duration in a wasm build that would be harmless on native.
Related errors
AI-assisted analysis of seanmonstar/reqwest@17e9bcb51c (2026-08-06).
Data as JSON: /data/errors/ff3953c9e8f01026.json.
Report an issue: GitHub.