-
Notifications
You must be signed in to change notification settings - Fork 209
Expand file tree
/
Copy pathwith-redis-lock.ts
More file actions
49 lines (42 loc) · 1.15 KB
/
Copy pathwith-redis-lock.ts
File metadata and controls
49 lines (42 loc) · 1.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { randomUUID } from 'crypto';
import { redis } from './redis';
interface RedisLockOptions {
readonly ttlSeconds?: number;
}
// Compare-and-delete, so a holder can only ever release its own lock.
const RELEASE_LOCK_SCRIPT = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
end
return 0
`;
export class LockNotAcquiredError extends Error {
public constructor(key: string) {
super(`Failed to acquire Redis lock for key="${key}"`);
this.name = 'LockNotAcquiredError';
}
}
export async function withRedisLock<TResult>(
key: string,
callback: () => Promise<TResult>,
options?: RedisLockOptions,
): Promise<TResult> {
const ttlSeconds = options?.ttlSeconds ?? 300;
const token = randomUUID();
const acquired = await redis.set(key, token, { nx: true, ex: ttlSeconds });
if (acquired !== 'OK') {
throw new LockNotAcquiredError(key);
}
try {
return await callback();
} finally {
try {
await redis.eval(RELEASE_LOCK_SCRIPT, [key], [token]);
} catch (releaseError) {
console.error(
`Failed to release Redis lock for key="${key}":`,
releaseError,
);
}
}
}