DirtyHairy/async-mutex

Cancel specific pending lock

Opened this issue · 5 comments

Hi @DirtyHairy thank you for the great lib!

I wanted to ask you if there's a way to cancel() some specific pending locks of a mutex? In the docs it states

Pending locks can be cancelled by calling cancel() on the mutex. This will reject all pending locks with E_CANCELED:

However, in some situations I want to cancel only a specific lock while leaving others waiting for the lock to become available. I took a quick look at the source code but didn't find a way of achieving this. Can you please provide some pointers about how to make this possible?
I guess that the runExclusive() will have to return an ID that later can be used to cancel (e.g., cancel(<ID>)) the pending lock.

Hi @AndreMaz !

Sorry for the late reply. Returning an ID would be the way to go, but this would mean that acquire and runExclusive would have to return two values, one being a promise, and one being the ID. This would mean that the result cannot be awaited directly, but it would have to be destructured, and the promise would have to be awaited.

Imo this makes those methods too quirky just to accomodate an edge case. The same effect can be easily achieved by the application by wrapping the promise returned by the library in another Promise and cancelling that.

Imo this makes those methods too quirky just to accomodate an edge case. The same effect can be easily achieved by the application by wrapping the promise returned by the library in another Promise and cancelling that.

What do you mean by wrapping the promise returned by another library and cancelling that. How would this end up cancelling a specific pending lock?

For example if I race lock acqusition like await Promise.race([lock.acquire(), abortP]); then if abortP were to reject first, then lock.acquire() would still be pending, although it would be a dangling promise that can be garbage collected... would that lock.acquire() dangling promise end up eventually acquiring the lock and preventing blocking other acquirers?

What do you mean by wrapping the promise returned by another library and cancelling that. How would this end up cancelling a specific pending lock?

You're right, I must've been in a hurry when I wrote the reply above, and the response is a bit misleading. What I really meant is something like:

function cancellableAcquire(mutex: Mutex): [Promise<void>, () => void] {
  let rejectFn: undefined | (() => void);
  let cancelled = false;

  return [
    new Promise((resolve, reject) => {
      rejectFn = reject;
      mutex.acquire.then(release => cancelled ? release() : resolve(release), reject);
    }),
    () => {
      cancelled = true;
      rejectFn?.();
    }
  ]
}

Consider allowing cancel() to just accept the original Promise back directly and finding it with ===. It would require a linear scan of the entire queue (worst case), but that's probably fast enough for most use cases.

Yeah, that would work. Of course, the danger with promises is that it is easy to keep track of the original promise, but we could add all Promises to a WeakSet before returning them and throw an exception if cancel is called on a 'foreign' promise.