Skip to content

Bug: _acquireConcurrency never removes a waiter whose signal aborts after it was queued #369

Description

@Jaydbrown

lib/token-allowance-gateway.ts:246-264_acquireConcurrency only handles signal?.aborted at enqueue time:

this._concurrencySemaphore.queue.push(entry);
if (signal?.aborted) { /* splice + reject */ }
// no signal.addEventListener('abort', ...) for a later abort

If the signal aborts after the entry is queued (the common case — the user cancels while waiting), nothing removes entry. When a slot later frees, _releaseConcurrency calls entry() which resolves a promise no one is awaiting, and the paired releaseConcurrency() in approve's finally may never run (the caller already threw from the mutex/abort path), leaking a permit.

Impact

Concurrency permits leak under cancellation; after _maxConcurrency cancelled-while-queued operations, the gateway deadlocks — no approve can acquire a slot.

Suggested fix

Mirror the Mutex implementation: attach an abort listener that dequeues and rejects the entry, with cleanup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave programbugSomething isn't workingpriority: mediumMissing feature or UX issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions