File: contexts/WalletContext.tsx — Mutex.acquire (:56-87), Semaphore.acquire (:116-146)
When a queued waiter is dequeued by _release(), its resolver first checks the
abort signal:
// Semaphore
resolver: (release) => {
if (signal?.aborted) { reject(new Error('Operation aborted')); return; } // <-- no release
resolve(release);
},
_release() has already shift()ed this entry off the queue and has not
incremented _available / cleared _locked (it delegated that to the resolver
by handing over release). The resolver rejects instead of resolving, so
release is never called.
Impact
If an AbortSignal fires in the exact window between "waiter is at the head of
the queue" and "_release dequeues and invokes it", that permit / the mutex lock
is lost forever. After maxConcurrentOperations such races the semaphore is
permanently exhausted and every subsequent signTx hangs; for the Mutex,
connect deadlocks.
Suggested fix
On the abort-at-dequeue path, still release: call the passed release() (or
this._release() / this._available++) before reject.
File:
contexts/WalletContext.tsx—Mutex.acquire(:56-87),Semaphore.acquire(:116-146)When a queued waiter is dequeued by
_release(), its resolver first checks theabort signal:
_release()has alreadyshift()ed this entry off the queue and has notincremented
_available/ cleared_locked(it delegated that to the resolverby handing over
release). The resolver rejects instead of resolving, soreleaseis never called.Impact
If an
AbortSignalfires in the exact window between "waiter is at the head ofthe queue" and "
_releasedequeues and invokes it", that permit / the mutex lockis lost forever. After
maxConcurrentOperationssuch races the semaphore ispermanently exhausted and every subsequent
signTxhangs; for the Mutex,connectdeadlocks.Suggested fix
On the abort-at-dequeue path, still release: call the passed
release()(orthis._release()/this._available++) beforereject.