UNPKG

redis-smq-common

Version:

Provides essential components and utilities shared across RedisSMQ packages.

197 lines 7.28 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Worker = void 0; const node_crypto_1 = require("node:crypto"); const path_1 = require("path"); const worker_threads_1 = require("worker_threads"); const index_js_1 = require("../async/index.js"); const index_js_2 = require("../env/index.js"); const index_js_3 = require("../event/index.js"); const index_js_4 = require("./errors/index.js"); const index_js_5 = require("./types/index.js"); const worker_logger_js_1 = require("./worker-logger.js"); const dir = index_js_2.env.getCurrentDir(); const allWorkers = new Set(); const cleanupAllWorkers = () => { if (allWorkers.size === 0) return; const tasks = Array.from(allWorkers).map((worker) => { return (cb) => { worker.shutdown(() => cb()); }; }); index_js_1.async.parallel(tasks, () => void 0); }; process.once('SIGTERM', cleanupAllWorkers); process.once('SIGINT', cleanupAllWorkers); process.once('beforeExit', cleanupAllWorkers); class Worker extends index_js_3.EventEmitter { constructor(workerFilename, initialPayload, logger) { super(); this.workerThread = null; this.stdoutStream = null; this.stderrStream = null; this.isShuttingDown = false; this.id = (0, node_crypto_1.randomUUID)(); this.workerFilename = workerFilename; this.initialPayload = initialPayload; this.logger = logger.createLogger(this.constructor.name); allWorkers.add(this); } cleanupStreams() { var _a, _b, _c, _d; if (this.stdoutStream) { try { (_b = (_a = this.workerThread) === null || _a === void 0 ? void 0 : _a.stdout) === null || _b === void 0 ? void 0 : _b.unpipe(this.stdoutStream); this.stdoutStream.end(); this.stdoutStream.destroy(); this.stdoutStream = null; } catch (error) { this.logger.debug(`Error cleaning up stdout stream: ${error}`); } } if (this.stderrStream) { try { (_d = (_c = this.workerThread) === null || _c === void 0 ? void 0 : _c.stderr) === null || _d === void 0 ? void 0 : _d.unpipe(this.stderrStream); this.stderrStream.end(); this.stderrStream.destroy(); this.stderrStream = null; } catch (error) { this.logger.debug(`Error cleaning up stderr stream: ${error}`); } } } getWorkerThread() { if (this.isShuttingDown) { throw new index_js_4.WorkerIsShuttingDownError(); } if (!this.workerThread) { const workerThreadPath = (0, path_1.resolve)(dir, './worker-thread/worker-thread.js'); this.stdoutStream = new worker_logger_js_1.WorkerLogger(false); this.stderrStream = new worker_logger_js_1.WorkerLogger(true); const workerOptions = { workerData: { filename: this.workerFilename, initialPayload: this.initialPayload, type: this.type, }, stdout: true, stderr: true, }; this.workerThread = new worker_threads_1.Worker(workerThreadPath, workerOptions); const workerStdout = this.workerThread.stdout; const workerStderr = this.workerThread.stderr; if (workerStdout && this.stdoutStream) { workerStdout.pipe(this.stdoutStream); workerStdout.on('error', (err) => { this.logger.debug(`Worker stdout error: ${err.message}`); }); } if (workerStderr && this.stderrStream) { workerStderr.pipe(this.stderrStream); workerStderr.on('error', (err) => { this.logger.debug(`Worker stderr error: ${err.message}`); }); } this.setupWorkerListeners(); this.logger.debug('Worker thread created with custom stream handling'); } return this.workerThread; } setupWorkerListeners() { if (!this.workerThread) return; this.workerThread.removeAllListeners(); this.workerThread.on('messageerror', (err) => { this.logger.error(`Message error: ${err.message}`); }); this.workerThread.on('error', (err) => { this.logger.error(`Worker error: ${err.message}`); this.emit('worker.error', err); this.cleanupStreams(); }); this.workerThread.on('exit', (code) => { this.logger.debug(`Worker exited with code ${code}`); this.cleanupStreams(); this.workerThread = null; this.emit('worker.terminated'); }); } postMessage(message, callback) { if (!callback) { callback = (err, reply) => { if (err) this.emit('worker.error', err); if (reply) this.emit('worker.data', reply); }; } if (this.isShuttingDown) { return callback(new index_js_4.WorkerIsShuttingDownError()); } const worker = this.getWorkerThread(); const onMessage = (msg) => { worker.removeListener('message', onMessage); worker.removeListener('exit', onExit); if (msg.code === index_js_5.EWorkerThreadChildExecutionCode.OK) { callback(null, msg.data); } else { callback(new index_js_4.WorkerThreadError({ metadata: msg })); } }; const onExit = (code) => { worker.removeListener('message', onMessage); worker.removeListener('exit', onExit); const error = code !== 0 ? new index_js_4.WorkerThreadFailureError({ metadata: { code, }, }) : null; callback(error); }; worker.once('message', onMessage); worker.once('exit', onExit); worker.postMessage(message); } shutdown(cb) { if (this.isShuttingDown) { return cb(new index_js_4.WorkerIsShuttingDownError()); } this.isShuttingDown = true; if (!this.workerThread) { this.cleanupStreams(); this.removeFromGlobalList(); return cb(null); } this.workerThread.removeAllListeners(); this.workerThread .terminate() .then(() => { this.cleanupStreams(); this.workerThread = null; this.isShuttingDown = false; this.removeFromGlobalList(); cb(null); }) .catch((err) => { this.isShuttingDown = false; cb(err); }); } removeFromGlobalList() { allWorkers.delete(this); } getId() { return this.id; } getWorkerFilename() { return this.workerFilename; } } exports.Worker = Worker; //# sourceMappingURL=worker.js.map