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

  1. 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()`.
  2. Pass `None` (no `.timeout()` call) instead of a giant duration when you mean 'unlimited'.
  3. 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

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.