diff --git a/src/index.ts b/src/index.ts index 850400e..7f43920 100644 --- a/src/index.ts +++ b/src/index.ts @@ -148,15 +148,19 @@ export class Lock { public readonly resources: string[], public readonly value: string, public readonly attempts: ReadonlyArray>, - public expiration: number + public expiration: number, + public settings?: Partial ) {} - async release(): Promise { - return this.redlock.release(this); + async release(settings?: Partial): Promise { + return this.redlock.release(this, { ...this.settings, ...settings }); } - async extend(duration: number): Promise { - return this.redlock.extend(this, duration); + async extend(duration: number, settings?: Partial): Promise { + return this.redlock.extend(this, duration, { + ...this.settings, + ...settings, + }); } } @@ -325,7 +329,8 @@ export default class Redlock extends EventEmitter { resources, value, attempts, - start + duration - drift + start + duration - drift, + settings ); } catch (error) { // If there was an error acquiring the lock, release any partial lock @@ -404,7 +409,8 @@ export default class Redlock extends EventEmitter { existing.resources, existing.value, attempts, - start + duration - drift + start + duration - drift, + settings ); return replacement; @@ -448,15 +454,13 @@ export default class Redlock extends EventEmitter { // Wait before reattempting. if (attempts.length < maxAttempts) { await new Promise((resolve) => { - setTimeout( - resolve, - Math.max( - 0, - settings.retryDelay + - Math.floor((Math.random() * 2 - 1) * settings.retryJitter) - ), - undefined + const delay = Math.max( + 0, + settings.retryDelay + + Math.floor((Math.random() * 2 - 1) * settings.retryJitter) ); + + setTimeout(resolve, delay, undefined); }); } else { throw new ExecutionError( diff --git a/src/single.test.ts b/src/single.test.ts index 6a5990d..fd726ed 100644 --- a/src/single.test.ts +++ b/src/single.test.ts @@ -419,6 +419,52 @@ function run(namespace: string, redis: Client | Cluster): void { } }); + test(`${namespace} - individual locks can be delayed`, async (t) => { + try { + const redlock = new Redlock([redis]); + + const duration = Math.floor(Number.MAX_SAFE_INTEGER / 10); + + // Acquire a lock. + const lock = await redlock.acquire(["{redlock}cx"], duration); + t.is( + await redis.get("{redlock}cx"), + lock.value, + "The lock value was incorrect." + ); + t.is( + Math.floor((await redis.pttl("{redlock}cx")) / 200), + Math.floor(duration / 200), + "The lock expiration was off by more than 200ms" + ); + + // Attempt to acquire another lock on the same resource. + const lock2Promise = redlock.acquire(["{redlock}cx"], duration); + lock2Promise.catch(() => { + // Node has made some very bad decisions about how to handle promises + // that reject before they are handled. This is a workaround. + }); + + // Release the first lock. + await lock.release(); + t.is(await redis.get("{redlock}cx"), null); + + // Ensure the second lock was acquired. + const lock2 = await lock2Promise; + t.is( + await redis.get("{redlock}cx"), + lock2.value, + "The lock value was incorrect." + ); + + // Release the second lock. + await lock2.release(); + t.is(await redis.get("{redlock}cx"), null); + } catch (error) { + fail(t, error); + } + }); + test(`${namespace} - the \`using\` helper acquires, extends, and releases locks`, async (t) => { try { const redlock = new Redlock([redis]);