Cancel doesn't work as expected
ivstiv opened this issue · 1 comments
ivstiv commented
It looks like when I cancel a mutex it doesn't throw the E_CANCELLED error and the runExclusive callback finishes.
Here is the code to reproduce it:
const mutex = new Mutex();
const longOperation = async () => {
// wait 5 seconds
await new Promise(res => setTimeout(res, 5000));
};
const execute = async (id: number) => {
console.log("Executing", id);
if(mutex.isLocked()) {
console.log("Mutex is locked", id);
mutex.cancel();
mutex.release();
}
await mutex.runExclusive(async () => {
await longOperation();
});
console.log("Execute finished running", id);
};
void execute(1);
void execute(2);
Output:
Executing 1
Executing 2
Mutex is locked 2
// 5 seconds pass...
Execute finished running 1
Execute finished running 2
Expected output:
Executing 1
Executing 2
Mutex is locked 2
thrown E_CANCELLED error by the first execution
// 5 seconds pass...
Execute finished running 2
Note 1: I am calling the release() because of this behaviour:
Note that while all pending locks are cancelled, a currently held lock will not be revoked. In consequence, the mutex may not be available even after cancel() has been called.
DirtyHairy commented
Hi @ivstiv!
What you describe is expected behaviour. Cancelling the mutex will reject all promises that have been acquired by waiters, but will (and cannot) cancel locks that already have been acquired. So, what happens in your example:
- First call to
execute
(first timeslice):runExclusive
callsacquire
, which locks the mutex and returns a resolved promise - Second call to
execute
(still in the same timeslice): all waiters are cancelled (but none are actually cancelled, asacquire
already locked the mutex and resolved the promise). The call torelease()
will force the mutex to become available again, but it cannot cancel the already acquired lock either.runExclusive
calls acquire, which locks the mutex again and returns a resolved promise - Second and third timeslice: the two invocations of
runExclusive
continue execution and await the timeout - fourth and fifth timeslice: the two timeouts have passed, and
console.log
is executed twice.
The important parts are:
- Although
acquire
returns a promise, the lock actually happens in the same timeslice if the mutex is available whenacquire
is called. - Calling
release
just forces the mutex to become available again, it cannot cancel resolved promises that were returned byacquire
. This is whyrelease
should only be called as the consequence of a previously acquired lock.