Timeouts
Bound each execution attempt; timed-out jobs fail and may retry.
Bound each execution attempt; timed-out jobs fail and may retry.
timeoutMs bounds a single execution attempt. The dispatcher races the task
against the timeout (tokio::time::timeout); if it elapses, the attempt is
marked failed and timed-out.
queue.task("scrape", scrape, { timeoutMs: 30_000 });
queue.enqueue("scrape", [url], { timeoutMs: 10_000 }); // per-job overrideA timeout counts as a failure: it consumes a retry and, if retries remain, the
job is re-enqueued. When the budget is exhausted the job dead-letters with
timedOut: true on the outcome — passed to the onRetry / onDeadLetter
middleware hooks and the job.retrying
/ job.dead events, so handlers can tell
a timeout apart from any other error.
A timeout ends the attempt, not the task. JavaScript cannot interrupt a
promise, so the handler keeps running after its attempt is marked failed, and
the worker frees its slot, so the next job can start beside it — this job's
own retry included, which repeats whatever side effects the first handler is
still performing. The task's
AbortSignal does not abort on a timeout —
only on a cancel request. Give the work its own deadline, shorter than
timeoutMs so the handler stops and cleans up before the attempt is failed,
and keep its side effects idempotent.
Combine that deadline with the task's signal, so a cancel request still reaches the work:
queue.task(
"scrape",
async (url: string) => {
const { signal } = currentJob()!;
// Stops on a cancel request or after 25s, whichever comes first.
const deadline = AbortSignal.any([signal, AbortSignal.timeout(25_000)]);
const res = await fetch(url, { signal: deadline });
return res.text();
},
{ timeoutMs: 30_000 },
);AbortSignal.any needs Node 20.3 or later.
Always set a timeout on production tasks — without one, a wedged task can hold a worker slot indefinitely.
Coming from BullMQ? BullMQ has no per-job timeout option; its docs suggest an
AbortControllerinside the processor. flexiq'stimeoutMsfails the attempt without one, but as above it does not stop the handler, so keep the deadline on the work itself.