Repository navigation
feat: cancel queued jobs with an AbortSignal - #32
Merged
Merged
Conversation
sderrow
marked this pull request as ready for review
October 15, 2026 04:06
Once the signal aborts, a failed attempt ends the job as a cancellation (rejects with signal.reason, no "failed" event, no retry), and a retry whose delay elapses after the abort is skipped without running the task. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Jobs accept a new
signaloption. If the signal aborts before the job starts, the job leaves the queue, its task never runs, and its promise rejects withsignal.reason. This follows the web-platform convention: callers get anAbortError, or aTimeoutErrorfromAbortSignal.timeout(ms), and the job never settles silently. That holds even withrejectOnDrop: false, since the caller asked for the abort.Jobs that have already started are never touched. Bottleneck can't stop running code, and freeing the slot early would let the next job start while the abandoned work is still in flight, which breaks the rate limit. To cancel work in flight, pass the same signal into the task.
Design notes
signal.reason, thefailedevent doesn't fire, the job isn't retried, and the reason's stack isn't annotated with schedule locations (the caller owns the reason object and may share it across many jobs). If the abort lands during a retry delay, the retry is skipped without running the task. This covers chained limiters, where an abort in the inner queue rejects the outer job, and tasks that pass the signal tofetchand throw its reason. The trade-off: an unrelated error from an attempt that fails after the abort surfaces assignal.reason, matching howfetchbehaves once aborted.abortlistener. This avoidsMaxListenersExceededWarningon runtimes that warn forAbortSignal, and avoids O(n²) listener churn when thousands of jobs share one controller.emptynow fires when an abort leaves the queue empty; before, it only fired on dispatch, soonce("empty")could hang.minTime.lightbuild: noDOMException,AbortSignal.timeout,throwIfAbortedornode:events. Wheresignal.reasondoesn't exist (before Node 16.14), the job rejects with aBottleneckErrorwith the new codeABORTED.How do we know it works
test/abort.test.tsruns against the local, ioredis and node-redis datastores. It holds state with deferred signals and barriers, with no sleeps, and covers:AbortSignal.timeoutrunning()is back to 0failednot called)The light-bundle smoke test also aborts a queued job. Coverage confirms every new branch runs.