UNPKG

redis-smq-common

Version:

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

190 lines 8.16 kB
import { readdir } from 'fs'; import path from 'path'; import { async } from '../async/index.js'; import { AbortError } from '../errors/index.js'; import { RedisLock } from '../redis-lock/index.js'; import { PowerSwitch } from '../power-switch/index.js'; import { Runnable } from '../runnable/index.js'; import { WorkerRunnable } from './worker-runnable.js'; export class WorkerResourceGroup extends Runnable { powerManager; locker; redisClient; logger; workers = []; runWorkersLocked = false; constructor(redisClient, logger, resourceGroupId) { super(); this.powerManager = new PowerSwitch(); this.logger = logger; this.logger.info(`Initializing WorkerResourceGroup with ID: ${resourceGroupId}`); this.redisClient = redisClient; this.redisClient.once('error', (err) => { this.logger.error(`Redis client error: ${err.message}`, err); this.handleError(err); }); this.logger.debug(`Creating RedisLock for resource group: ${resourceGroupId}`); this.locker = new RedisLock(redisClient, logger, resourceGroupId, 60000, true, 15000); this.locker.on('locker.error', (err) => { this.logger.error(`Locker error: ${err.message}`, err); this.handleError(err); }); this.logger.info(`WorkerResourceGroup initialized with ID: ${resourceGroupId}`); } lock = (cb) => { this.logger.debug('Attempting to acquire lock...'); this.locker.acquireLock((err, acquired) => { if (err) { this.logger.error(`Failed to acquire lock: ${err.message}`, err); cb(err); } else if (acquired) { this.logger.info(`Workers are exclusively running from this instance (Lock ID ${this.locker.getId()}).`); cb(); } else { this.logger.warn('Lock was not acquired, but no error was returned'); cb(); } }); }; runWorkers = (cb) => { if (!this.runWorkersLocked) { this.logger.info(`Starting ${this.workers.length} workers...`); this.runWorkersLocked = true; async.eachOf(this.workers, (worker, index, done) => { this.logger.debug(`Starting worker ${index + 1}/${this.workers.length} (ID: ${worker.getId()})`); worker.run((err) => { if (err) { this.logger.error(`Failed to start worker ${index + 1}/${this.workers.length} (ID: ${worker.getId()}): ${err.message}`, err); } else { this.logger.debug(`Worker ${index + 1}/${this.workers.length} (ID: ${worker.getId()}) started successfully`); } done(err); }); }, (err) => { this.runWorkersLocked = false; if (err) { this.logger.error(`Error starting workers: ${err.message}`, err); } else { this.logger.info(`All ${this.workers.length} workers started successfully`); } cb(err); }); } else { this.logger.warn('Attempted to run workers while already locked'); cb(new AbortError()); } }; shutDownWorkers = (cb) => { if (!this.runWorkersLocked) { this.logger.info(`Shutting down ${this.workers.length} workers...`); this.runWorkersLocked = true; async.eachOf(this.workers, (worker, index, done) => { this.logger.debug(`Shutting down worker ${index + 1}/${this.workers.length} (ID: ${worker.getId()})`); worker.shutdown((err) => { if (err) { this.logger.warn(`Error shutting down worker ${index + 1}/${this.workers.length} (ID: ${worker.getId()}): ${err.message}`); } else { this.logger.debug(`Worker ${index + 1}/${this.workers.length} (ID: ${worker.getId()}) shut down successfully`); } done(); }); }, () => { this.workers = []; this.runWorkersLocked = false; this.logger.info('All workers have been shut down'); cb(); }); } else { this.logger.debug('Workers shutdown requested but workers are locked, retrying in 1 second...'); setTimeout(() => this.shutDownWorkers(cb), 1000); } }; releaseLock = (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.info('Lock released successfully'); } cb(err); }); }; getLogger() { return this.logger; } goingUp() { this.logger.debug('WorkerResourceGroup going up...'); return super.goingUp().concat([this.lock, this.runWorkers]); } goingDown() { this.logger.debug('WorkerResourceGroup going down...'); return [this.shutDownWorkers, this.releaseLock].concat(super.goingDown()); } handleError(err) { if (this.isRunning()) { this.logger.error(`WorkerResourceGroup error: ${err.message}`, err); this.emit('workerResourceGroup.error', err); super.handleError(err); } } addWorker = (filename, payload) => { this.logger.debug(`Adding worker from file: ${filename}`); const worker = new WorkerRunnable(filename, payload, this.logger); worker.on('worker.error', (err) => { this.logger.error(`Worker error from ${filename}: ${err.message}`, err); this.handleError(err); }); this.workers.push(worker); this.logger.info(`Worker added from file: ${filename}, total workers: ${this.workers.length}`); }; loadFromDir = (workersDir, payload, cb) => { if (this.isDown() && !this.isGoingUp()) { this.logger.info(`Loading workers from directory: ${workersDir}`); readdir(workersDir, (err, files) => { if (err) { this.logger.error(`Failed to read workers directory ${workersDir}: ${err.message}`, err); cb(err); } else { this.logger.debug(`Found ${files.length} files in workers directory`); let workerCount = 0; async.eachOf(files ?? [], (file, index, done) => { if (file.endsWith('.worker.js')) { const filepath = path.resolve(workersDir, file); this.logger.debug(`Loading worker file ${index + 1}/${files.length}: ${file}`); this.addWorker(filepath, payload); workerCount++; done(); } else { this.logger.debug(`Skipping non-worker file: ${file}`); done(); } }, (err) => { if (err) { this.logger.error(`Error loading workers: ${err.message}`, err); } else { this.logger.info(`Successfully loaded ${workerCount} workers from ${files.length} files`); } cb(err); }); } }); } else { this.logger.warn('Cannot load workers: WorkerResourceGroup is not in DOWN state'); cb(new Error('Cannot load workers: WorkerResourceGroup is not in DOWN state')); } }; } //# sourceMappingURL=worker-resource-group.js.map