redis-smq-common
Version:
RedisSMQ Common Library provides many components that are mainly used by RedisSMQ and RedisSMQ Monitor.
226 lines • 9.15 kB
JavaScript
import { resolve } from 'path';
import { env } from '../env/index.js';
import { AbortError } from '../errors/index.js';
import { Runnable } from '../runnable/index.js';
import { Timer } from '../timer/index.js';
import { LockAcquireError, LockExtendError, LockMethodNotAllowedError, LockNotAcquiredError, } from './errors/index.js';
const dir = env.getCurrentDir();
var ELuaScript;
(function (ELuaScript) {
ELuaScript["RELEASE_LOCK"] = "RELEASE_LOCK";
ELuaScript["EXTEND_LOCK"] = "EXTEND_LOCK";
})(ELuaScript || (ELuaScript = {}));
const luaScriptMap = {
[ELuaScript.RELEASE_LOCK]: resolve(dir, './lua-scripts/release-lock.lua'),
[ELuaScript.EXTEND_LOCK]: resolve(dir, './lua-scripts/extend-lock.lua'),
};
export class RedisLock extends Runnable {
lockKey;
retryOnFail;
ttl;
redisClient;
autoExtendInterval;
timer;
logger;
constructor(redisClient, logger, lockKey, ttl, retryOnFail = false, autoExtendInterval = 0) {
super();
this.lockKey = lockKey;
this.ttl = ttl;
this.retryOnFail = retryOnFail;
this.autoExtendInterval = autoExtendInterval;
this.logger = logger;
this.logger.info(`RedisLock instance created for key: ${lockKey}`);
this.logger.debug('RedisLock initialization details', {
id: this.id,
lockKey,
ttl,
retryOnFail,
autoExtendInterval,
});
this.redisClient = redisClient;
this.redisClient.on('error', this.handleError);
this.logger.debug('Loading Redis Lua scripts');
this.redisClient.loadScriptFiles(luaScriptMap, (err) => {
if (err) {
this.logger.error(`Failed to load Redis Lua scripts: ${err.message}`, err);
}
else {
this.logger.debug('Redis Lua scripts loaded successfully');
}
});
this.timer = new Timer();
this.timer.on('error', this.handleError);
this.logger.info('RedisLock initialization complete');
}
lock = (cb) => {
this.logger.debug(`Attempting to acquire lock for key: ${this.lockKey}`);
this.redisClient.set(this.lockKey, this.id, {
expire: { mode: 'PX', value: this.ttl },
exists: 'NX',
}, (err, reply) => {
if (err) {
this.logger.error(`Error acquiring lock: ${err.message}`, err);
return cb(err);
}
if (!this.powerSwitch.isGoingUp()) {
this.logger.warn('Lock acquisition aborted: power switch is no longer in going-up state');
return cb(new AbortError());
}
if (!reply) {
if (this.retryOnFail) {
this.logger.debug('Lock already held by another instance, retrying in 1 second');
return this.timer.setTimeout(() => this.lock(cb), 1000);
}
else {
this.logger.warn('Failed to acquire lock: already held by another instance');
return cb(new LockAcquireError());
}
}
this.logger.info(`Lock acquired successfully for key: ${this.lockKey}`);
cb();
});
};
extend = (cb) => {
if (!this.isRunning()) {
this.logger.warn('Cannot extend lock: lock is not currently held');
return cb(new LockNotAcquiredError());
}
this.logger.debug(`Attempting to extend lock TTL for key: ${this.lockKey}`);
this.redisClient.runScript(ELuaScript.EXTEND_LOCK, [this.lockKey], [this.id, this.ttl], (err, reply) => {
if (err) {
this.logger.error(`Error extending lock TTL: ${err.message}`, err);
return cb(err);
}
if (!this.powerSwitch.isRunning()) {
this.logger.warn('Lock extension aborted: power switch is no longer in running state');
return cb(new AbortError());
}
if (reply !== 1) {
this.logger.warn('Failed to extend lock: lock no longer held by this instance');
return this.shutdown(() => cb(new LockExtendError()));
}
this.logger.debug(`Lock TTL extended successfully for key: ${this.lockKey}`);
cb();
});
};
release = (cb) => {
this.logger.debug(`Attempting to release lock for key: ${this.lockKey}`);
this.redisClient.runScript(ELuaScript.RELEASE_LOCK, [this.lockKey], [this.id], (err) => {
if (err) {
this.logger.error(`Error releasing lock: ${err.message}`, err);
}
else {
this.logger.info(`Lock released successfully for key: ${this.lockKey}`);
}
cb(err);
});
};
resetTimer = (cb) => {
this.logger.debug('Resetting auto-extension timer');
this.timer.reset();
this.logger.debug('Auto-extension timer reset successfully');
cb();
};
autoExtendLock() {
if (this.autoExtendInterval) {
this.logger.debug(`Scheduling auto-extension of lock in ${this.autoExtendInterval}ms`);
this.timer.setTimeout(() => this.extend((err) => {
if (err && !(err instanceof AbortError)) {
this.logger.error(`Auto-extension of lock failed: ${err.message}`, err);
this.handleError(err);
}
else if (!err) {
this.logger.debug('Auto-extension of lock successful, scheduling next extension');
this.autoExtendLock();
}
else {
this.logger.debug('Auto-extension of lock aborted');
}
}), this.autoExtendInterval);
}
else {
this.logger.debug('Auto-extension of lock is disabled');
}
}
goingUp() {
this.logger.debug('RedisLock transitioning to going-up state');
this.emit('locker.goingUp', this.id);
return super.goingUp().concat([this.lock]);
}
goingDown() {
this.logger.debug('RedisLock transitioning to going-down state');
this.emit('locker.goingDown', this.id);
return [this.resetTimer, this.release].concat(super.goingDown());
}
handleError = (err) => {
this.logger.error(`RedisLock error: ${err.message}`, err);
this.emit('locker.error', err, this.id);
super.handleError(err);
};
getLogger() {
return this.logger;
}
up(cb) {
this.logger.info(`RedisLock transitioned to up state for key: ${this.lockKey}`);
this.emit('locker.up', this.id);
super.up(cb);
}
down(cb) {
this.logger.info(`RedisLock transitioned to down state for key: ${this.lockKey}`);
this.emit('locker.down', this.id);
super.down(cb);
}
run(cb) {
this.logger.info(`Attempting to run RedisLock for key: ${this.lockKey}`);
super.run((err, reply) => {
if (err instanceof LockAcquireError) {
this.logger.info(`Lock already held by another instance for key: ${this.lockKey}`);
return cb(null, false);
}
if (err) {
this.logger.error(`Error running RedisLock: ${err.message}`, err);
return cb(err);
}
if (reply) {
this.logger.info(`RedisLock running successfully for key: ${this.lockKey}`);
if (this.autoExtendInterval) {
this.logger.debug(`Auto-extension enabled with interval: ${this.autoExtendInterval}ms`);
this.autoExtendLock();
}
}
cb(null, Boolean(reply));
});
}
acquireLock(cb) {
this.logger.info(`Acquiring lock for key: ${this.lockKey}`);
this.run(cb);
}
releaseLock(cb) {
this.logger.info(`Releasing lock for key: ${this.lockKey}`);
this.shutdown(cb);
}
extendLock(cb) {
this.logger.debug(`Manual extension of lock requested for key: ${this.lockKey}`);
if (this.autoExtendInterval) {
this.logger.warn('Cannot manually extend lock: auto-extension is enabled');
return cb(new LockMethodNotAllowedError());
}
if (!this.powerSwitch.isRunning()) {
this.logger.warn('Cannot extend lock: lock is not currently held');
return cb(new LockNotAcquiredError());
}
this.logger.debug('Extending lock TTL');
this.extend(cb);
}
isLocked() {
const locked = this.powerSwitch.isRunning();
this.logger.debug(`Lock status check for key ${this.lockKey}: ${locked ? 'locked' : 'not locked'}`);
return locked;
}
isReleased() {
const released = this.powerSwitch.isDown();
this.logger.debug(`Lock release status check for key ${this.lockKey}: ${released ? 'released' : 'not released'}`);
return released;
}
}
//# sourceMappingURL=redis-lock.js.map