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