UNPKG

redis-smq-common

Version:

Provides essential components and utilities shared across RedisSMQ packages.

135 lines 5.27 kB
import { AbortError } from '../../errors/index.js'; import { LockNotAcquiredError, RedisLock } from '../../redis-lock/index.js'; import { Runnable } from '../../runnable/index.js'; import { WorkerHandler } from './worker-handler.js'; import { WorkerLoader } from './worker-loader.js'; export class WorkerCluster extends Runnable { static LOCK_TTL = 60_000; uniqueGroupIdentifier = null; workerHandler; workerLoader; redisClient; logger; locker = null; constructor(redisClient, logger, uniqueGroupIdentifier, workerFilenamePattern = '.worker.js') { super(); this.logger = logger.createLogger(this.constructor.name); this.redisClient = redisClient; this.workerHandler = new WorkerHandler({ logger: this.logger }); this.workerLoader = new WorkerLoader(this.logger, workerFilenamePattern, this.handleWorkerError.bind(this), this.workerHandler); this.uniqueGroupIdentifier = uniqueGroupIdentifier; if (this.uniqueGroupIdentifier) { this.logger.debug(`Creating RedisLock for resource group: ${this.uniqueGroupIdentifier}`); this.locker = new RedisLock(this.redisClient, this.logger, this.uniqueGroupIdentifier, WorkerCluster.LOCK_TTL, true, true); this.locker.on('locker.error', (err) => { this.logger.error(`Locker error: ${err.message}`, err); this.handleError(err); }); } this.logger.debug(`WorkerCluster initialized`); } handleWorkerError(err, filename) { this.logger.error(`Worker error from ${filename}: ${err.message}`, err); this.handleError(err); } acquireLock = (cb) => { if (!this.locker) return cb(); if (!this.isOperational()) { cb(new Error('Resource group is shutting down')); return; } this.logger.debug('Attempting to acquire lock...'); this.locker.acquireLock((err) => { if (!err) { this.logger.debug(`Lock acquired successfully (Lock ID: ${this.locker?.getId()})`); return cb(); } if (err instanceof LockNotAcquiredError) { this.logger.warn('Could not acquire lock (already locked by another instance)'); return cb(new AbortError()); } this.logger.error(`Failed to acquire lock: ${err.message}`, err); cb(err); }); }; releaseLock = (cb) => { if (!this.locker) return cb(); this.logger.debug('Releasing lock...'); this.locker.releaseLock((err) => { if (err) { this.logger.error(`Failed to release lock: ${err.message}`, err); } else { this.logger.debug('Lock released successfully'); } cb(err); }); }; goingUp() { this.logger.debug('WorkerCluster going up...'); return [ ...super.goingUp(), this.acquireLock, (cb) => this.workerHandler.run(cb), ]; } goingDown() { this.logger.debug('WorkerCluster going down...'); return [ (cb) => this.workerHandler.shutdown(cb), this.releaseLock, ...super.goingDown(), ]; } handleError(err) { if (!this.isOperational()) return; this.logger.error(`WorkerCluster error: ${err.message}`, err); this.emit('workerCluster.error', err); super.handleError(err); } addWorker(filename, payload) { this.assertDownState('add workers'); this.logger.debug(`Adding worker from file: ${filename}`); const worker = this.workerLoader.createWorker(filename, payload); this.emit('workerCluster.workerAdded', worker); return worker; } loadFromDir = (workersDir, payload, cb) => { const stateError = this.validateDownState('load workers'); if (stateError) { cb(stateError); return; } if (this.workerHandler.hasWorkers()) { this.logger.warn('Cannot load workers: Workers already exist in resource group'); cb(new Error('Workers already exist, cannot load new ones')); return; } this.logger.debug(`Loading workers from directory: ${workersDir}`); this.workerLoader.loadFromDirectory(workersDir, payload, (err) => { if (!err) { this.logger.debug(`Successfully loaded workers from ${workersDir}`); } cb(err); }); }; assertDownState(operation) { const err = this.validateDownState(operation); if (err) throw err; } validateDownState(operation) { if (!this.isDown()) { this.logger.warn(`Cannot ${operation}: Resource group is not in DOWN state`); return new Error(`Resource group must be in DOWN state to ${operation}`); } if (this.isGoingUp()) { this.logger.warn(`Cannot ${operation}: Resource group is going up`); return new Error(`Cannot ${operation} while going up`); } } } //# sourceMappingURL=worker-cluster.js.map