UNPKG

redis-smq-common

Version:

Provides essential components and utilities shared across RedisSMQ packages.

182 lines 5.96 kB
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