redis-smq-common
Version:
Provides essential components and utilities shared across RedisSMQ packages.
182 lines • 5.96 kB
JavaScript
import { v4 as uuid } from 'uuid';
import { async } from '../async/index.js';
import { AbortError } from '../errors/index.js';
import { EventEmitter } from '../event/index.js';
import { PowerSwitch } from '../power-switch/index.js';
import { withOptionalCallback } from '../async/with-optional-callback.js';
export class Runnable extends EventEmitter {
id;
powerSwitch;
forceShutdownOnError;
waitingForUpCallbacks = [];
waitingForDownCallbacks = [];
constructor(forceShutdownOnError = true) {
super();
this.id = uuid();
this.powerSwitch = new PowerSwitch();
this.forceShutdownOnError = forceShutdownOnError;
}
goingUp() {
this.logger.debug(`Going up`);
return [];
}
goingDown() {
this.logger.debug(`Going down`);
return [];
}
finalizeUp() {
if (!this.powerSwitch.commit()) {
const err = new Error('Failed to commit "up" state');
this.flushWaitingForUp(err);
return;
}
this.flushWaitingForUp();
this.logger.debug(`Up and running`);
}
finalizeDown() {
this.powerSwitch.commit();
this.flushWaitingForUp(new Error('Runnable has been shut down'));
this.flushWaitingForDown();
this.logger.debug(`Completely shut down`);
}
handleError(err) {
if (!this.isOperational())
return;
this.logger.error(err);
this.shutdown(() => void 0);
}
flushWaitingForUp(error) {
const callbacks = this.waitingForUpCallbacks;
this.waitingForUpCallbacks = [];
for (const cb of callbacks) {
try {
cb(error ?? null);
}
catch (e) {
this.logger.error('Error in waitingForUp callback:', e);
}
}
}
flushWaitingForDown(error) {
const callbacks = this.waitingForDownCallbacks;
this.waitingForDownCallbacks = [];
for (const cb of callbacks) {
try {
cb(error ?? null);
}
catch (e) {
this.logger.error('Error in waitingForDown callback:', e);
}
}
}
executeGoingUpTasks() {
const tasks = this.goingUp().map((task) => (taskCb) => {
if (!this.isGoingUp()) {
return taskCb(new AbortError({ message: 'Startup aborted' }));
}
task((err) => {
if (!this.isGoingUp()) {
taskCb(new AbortError({ message: 'Startup aborted by shutdown' }));
}
else {
taskCb(err);
}
});
});
async.series(tasks, (err) => {
if (!this.isGoingUp()) {
return this.flushWaitingForUp(new AbortError({ message: 'Startup aborted by shutdown' }));
}
if (err) {
this.powerSwitch.rollback();
this.flushWaitingForUp(err);
if (this.forceShutdownOnError)
this.executeGoingDownTasks();
return;
}
this.finalizeUp();
});
}
executeGoingDownTasks() {
async.series(this.goingDown(), (taskErr) => {
if (taskErr) {
this.logger.error('Error in goingDown tasks:', taskErr);
}
this.finalizeDown();
});
}
isOperational() {
return this.isGoingUp() || (this.isUp() && !this.isGoingDown());
}
isRunning() {
return this.powerSwitch.isRunning();
}
isGoingUp() {
return this.powerSwitch.isGoingUp();
}
isGoingDown() {
return this.powerSwitch.isGoingDown();
}
isUp() {
return this.powerSwitch.isUp();
}
isDown() {
return this.powerSwitch.isDown();
}
run(cb) {
return withOptionalCallback(cb, (callback) => {
if (this.isRunning())
return callback(null);
if (this.isGoingUp())
return void this.waitingForUpCallbacks.push(callback);
if (this.isGoingDown())
return callback(new Error('Cannot run: shutdown is in progress'));
if (!this.powerSwitch.goingUp()) {
return callback(new Error('Cannot initiate startup – invalid state transition'));
}
this.waitingForUpCallbacks.push(callback);
this.executeGoingUpTasks();
});
}
shutdown(cb) {
return withOptionalCallback(cb, (callback) => {
this.waitingForDownCallbacks.push(callback);
if (this.isGoingDown())
return;
if (!this.isOperational()) {
this.flushWaitingForDown();
return;
}
if (this.isGoingUp()) {
this.powerSwitch.rollback();
this.flushWaitingForUp(new AbortError({ message: 'Startup aborted by shutdown' }));
this.executeGoingDownTasks();
return;
}
if (!this.powerSwitch.goingDown()) {
this.flushWaitingForDown(new Error('Cannot initiate shutdown – invalid state transition'));
return;
}
this.executeGoingDownTasks();
});
}
ensureIsOperational(cb) {
return withOptionalCallback(cb, (callback) => {
if (this.isGoingDown()) {
return callback(new AbortError({ message: 'Shutdown in progress' }));
}
if (this.isRunning()) {
return callback(null);
}
if (this.isGoingUp()) {
this.waitingForUpCallbacks.push((err) => callback(err));
return;
}
this.run(callback);
});
}
getId() {
return this.id;
}
}
//# sourceMappingURL=runnable.js.map