redis-smq-common
Version:
Provides essential components and utilities shared across RedisSMQ packages.
135 lines • 5.27 kB
JavaScript
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