fennec-be/src/commons/redis-mutex/redis-mutex.service.ts

72 lines
1.6 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { RedisService } from 'nestjs-redis';
import * as uuid from 'uuid';
import { ApplicationException } from '../exceptions/application.exception';
export interface RedisMutexOption {
/**
* seconds
*/
expires?: number;
/**
* seconds
*/
timeout?: number | null;
/**
* milliseconds
*/
retryDelay?: number;
}
@Injectable()
export class RedisMutexService {
constructor(private readonly redisClient: RedisService) {}
public async lock(
key: string,
{ expires = 100, timeout = 10, retryDelay = 100 }: RedisMutexOption = {
expires: 100,
timeout: 10,
retryDelay: 100,
},
) {
const redisKey = `${'mutex-lock'}:${key}`;
const redis = this.redisClient.getClient();
const value = uuid.v4();
const timeoutAt = timeout ? Date.now() + timeout * 1000 : null;
while (
!(await redis
.set(redisKey, value, 'EX', expires, 'NX')
.then(() => true)
.catch(() => false))
) {
if (timeoutAt && timeoutAt > Date.now()) {
throw new ApplicationException('lock timeout');
}
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
const renewTimer = setInterval(() => {
redis.expire(redisKey, expires);
}, (expires * 1000) / 2);
return async () => {
clearInterval(renewTimer);
await redis.eval(
`
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
`,
1,
redisKey,
value,
);
};
}
}