DirtyHairy / async-mutex

A mutex for synchronizing async workflows in Javascript
MIT License
1.14k stars 63 forks source link

Cancel specific pending lock #64

Open AndreMaz opened 2 years ago

AndreMaz commented 2 years ago

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.

DirtyHairy commented 1 year ago

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.

CMCDragonkai commented 1 year ago

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?

DirtyHairy commented 1 year ago

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?.();
    }
  ]
}
dmurvihill commented 9 months ago

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.

DirtyHairy commented 8 months ago

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.