Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,19 @@ export class Lock {
public readonly resources: string[],
public readonly value: string,
public readonly attempts: ReadonlyArray<Promise<ExecutionStats>>,
public expiration: number
public expiration: number,
public settings?: Partial<Settings>
) {}

async release(): Promise<ExecutionResult> {
return this.redlock.release(this);
async release(settings?: Partial<Settings>): Promise<ExecutionResult> {
return this.redlock.release(this, { ...this.settings, ...settings });
}

async extend(duration: number): Promise<Lock> {
return this.redlock.extend(this, duration);
async extend(duration: number, settings?: Partial<Settings>): Promise<Lock> {
return this.redlock.extend(this, duration, {
...this.settings,
...settings,
});
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -404,7 +409,8 @@ export default class Redlock extends EventEmitter {
existing.resources,
existing.value,
attempts,
start + duration - drift
start + duration - drift,
settings
);

return replacement;
Expand Down Expand Up @@ -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(
Expand Down
46 changes: 46 additions & 0 deletions src/single.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down