UNPKG

redis-smq-common

Version:

RedisSMQ Common Library provides many components that are mainly used by RedisSMQ and RedisSMQ Monitor.

226 lines 9.15 kB
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