connor4312/cockatiel
BulkheadPolicy queue stalls when dequeued item has an already-aborted signal
Summary
Context: The
BulkheadPolicyimplements a concurrency limiter with a queue for excess requests that are processed when execution slots become available.Bug: When a queued item with an already-aborted signal is dequeued and starts execution, it throws
TaskCancelledErrorbefore entering the main execution path, which prevents thedequeue()method from being called again.Actual vs. expected: The queue processing stalls after a cancelled item is dequeued, leaving subsequent items in the queue unprocessed; the expected behavior is that queue processing continues and subsequent items are dequeued normally.
Impact: Applications using
BulkheadPolicywith cancellation can experience permanent queue stalls, causing subsequent queued operations to never execute and potentially leading to resource leaks and hung promises.
Code with bug
Logical proof
Precondition: A queued item’s
signalis aborted by the time it is dequeued.dequeue()shifts the item and callsthis.execute(item.fn, item.signal). Becausesignal.aborted === true,execute()throwsTaskCancelledErrorimmediately (before entering itstry/finally).The
finallythat decrementsactiveand callsdequeue()is never reached, so no follow-up dequeue occurs for remaining items.The promise chain’s
.catch(item.reject)rejects the cancelled item’s promise but does not continue processing the queue. Withactivepotentially0andqueuenon-empty, processing stalls indefinitely.
Recommended fix
The fix is to ensure dequeue() is called to process the next queued item even when the current item’s execution throws before entering the main execution path. There are two possible approaches:
Option 1: Check signal status in dequeue() before calling execute()
Move the abort check earlier, before calling execute():
Option 1 is recommended because it:
Is simpler and more direct
Handles cancellation earlier (fail-fast)
Doesn’t create additional promise chain overhead
Makes the control flow more explicit
Matches the existing pattern of checking cancellation early