redis-smq-common
Version:
RedisSMQ Common Library provides many components that are mainly used by RedisSMQ and RedisSMQ Monitor.
245 lines • 10.2 kB
JavaScript
import { randomUUID } from 'node:crypto';
import { resolve } from 'path';
import { Worker as WorkerThread } from 'worker_threads';
import { env } from '../env/index.js';
import { EventEmitter } from '../event/index.js';
import { logger } from '../logger/index.js';
import { WorkerThreadError } from './errors/index.js';
import { EWorkerThreadChildExecutionCode, EWorkerThreadChildExitCode, EWorkerThreadParentMessage, EWorkerType, } from './types/index.js';
const dir = env.getCurrentDir();
export class Worker extends EventEmitter {
id;
workerFilename;
initialPayload;
workerThread = null;
logger;
constructor(workerFilename, initialPayload) {
super();
this.id = randomUUID();
this.workerFilename = workerFilename;
this.initialPayload = initialPayload;
this.logger = logger.getLogger({ enabled: false });
this.logger.info(`Worker instance created for ${workerFilename}`);
this.logger.debug('Worker initialization details', {
id: this.id,
filename: workerFilename,
initialPayload: this.initialPayload ? 'provided' : 'none',
});
}
getId() {
return this.id;
}
getWorkerThread() {
if (!this.workerThread) {
const workerThreadPath = resolve(dir, './worker-thread/worker-thread.js');
this.logger.info(`Creating new worker thread from ${workerThreadPath}`);
this.logger.debug('Worker thread configuration', {
filename: this.workerFilename,
type: EWorkerType[this.type],
workerThreadPath,
});
try {
this.workerThread = new WorkerThread(workerThreadPath, {
workerData: {
filename: this.workerFilename,
initialPayload: this.initialPayload,
type: this.type,
},
stdout: true,
});
this.logger.debug('Worker thread created successfully');
if (this.workerThread.stdout) {
this.logger.debug('Setting up stdout capture for worker thread');
this.workerThread.stdout.on('data', (data) => {
const output = data.toString().trim();
if (output) {
this.logger.debug(output);
}
});
}
else {
this.logger.warn('Worker thread stdout is not available');
}
if (this.workerThread.stderr) {
this.logger.debug('Setting up stderr capture for worker thread');
this.workerThread.stderr.on('data', (data) => {
const output = data.toString().trim();
if (output) {
this.logger.error(output);
}
});
}
else {
this.logger.warn('Worker thread stderr is not available');
}
this.workerThread.on('messageerror', (err) => {
this.logger.error(`Worker message deserialization error: ${err.message}`, {
error: err.message,
stack: err.stack,
name: err.name,
});
});
this.workerThread.on('error', (err) => {
this.logger.error(`Worker uncaught exception: ${err.message}`, {
error: err.message,
stack: err.stack,
name: err.name,
});
});
this.workerThread.on('exit', (code) => {
if (code === 0) {
this.logger.info('Worker thread exited successfully with code 0');
}
else {
this.logger.warn(`Worker thread exited with code ${code}`);
}
this.workerThread = null;
});
}
catch (err) {
const error = err instanceof Error ? err : new Error('Unknown error');
this.logger.error(`Failed to create worker thread: ${error.message}`, {
error: error.message,
stack: error.stack,
});
throw err;
}
}
else {
this.logger.debug('Reusing existing worker thread');
}
return this.workerThread;
}
registerEvents(cb) {
try {
const worker = this.getWorkerThread();
this.logger.debug('Registering worker thread event handlers');
const cleanUp = () => {
this.logger.debug('Cleaning up worker thread event listeners');
worker
.removeListener('message', onMessage)
.removeListener('exit', onExit);
};
const callback = (err, data) => {
if (err) {
this.logger.error(`Worker callback error: ${err.message}`, {
error: err.message,
stack: err.stack,
name: err.name,
});
if (cb instanceof Worker) {
this.logger.debug('Emitting worker.error event');
this.emit('worker.error', err);
}
else {
this.logger.debug('Calling error callback');
cb(err);
}
}
else {
this.logger.debug('Worker callback success');
if (cb instanceof Worker) {
this.logger.debug('Emitting worker.data event');
this.emit('worker.data', data);
}
else {
this.logger.debug('Calling success callback');
cb(null, data);
}
}
};
const onMessage = (msg) => {
this.logger.debug(`Received message from worker thread with code ${msg.code}`, {
code: msg.code,
hasData: msg.data !== undefined,
hasError: msg.error !== null && msg.error !== undefined,
});
cleanUp();
if (msg.code !== EWorkerThreadChildExecutionCode.OK) {
const errorCode = EWorkerThreadChildExecutionCode[msg.code] ||
EWorkerThreadChildExitCode[msg.code] ||
`Unknown(${msg.code})`;
this.logger.error(`Worker thread execution error: ${errorCode}`, {
code: msg.code,
errorCode,
error: msg.error,
});
callback(new WorkerThreadError(msg));
}
else {
this.logger.debug('Worker thread execution successful');
callback(null, msg.data);
}
};
const onExit = (code) => {
this.logger.warn(`Worker thread exited unexpectedly with code ${code}`);
cleanUp();
const msg = {
code: EWorkerThreadChildExitCode.TERMINATED,
error: null,
};
this.logger.error('Worker thread terminated', {
exitCode: code,
errorCode: EWorkerThreadChildExitCode[EWorkerThreadChildExitCode.TERMINATED],
});
callback(new WorkerThreadError(msg));
};
worker.once('message', onMessage);
worker.once('exit', onExit);
this.logger.debug('Worker thread event handlers registered successfully');
}
catch (err) {
this.logger.error(`Failed to register worker thread events: ${err instanceof Error ? err.message : 'unknown error'}`, err);
throw err;
}
}
postMessage(message) {
try {
this.logger.debug(`Posting message to worker thread: ${EWorkerThreadParentMessage[message.type]}`, {
messageType: EWorkerThreadParentMessage[message.type],
hasPayload: 'payload' in message && message.payload !== undefined,
});
const worker = this.getWorkerThread();
worker.postMessage(message);
this.logger.debug('Message posted successfully to worker thread');
}
catch (err) {
const error = err instanceof Error ? err : new Error('Unknown error');
this.logger.error(`Failed to post message to worker thread: ${error.message}`, {
error: error.message,
stack: error.stack,
messageType: message.type,
});
throw err;
}
}
shutdown(cb) {
this.logger.info('Shutting down worker thread');
const callback = () => {
this.logger.info('Worker thread shutdown complete');
this.workerThread = null;
cb();
};
if (this.workerThread) {
this.logger.debug('Terminating active worker thread');
this.workerThread
.terminate()
.then((code) => {
this.logger.debug(`Worker thread terminated with exit code ${code}`);
callback();
})
.catch((err) => {
this.logger.error(`Error terminating worker thread: ${err.message}`, {
error: err.message,
stack: err.stack,
});
callback();
});
}
else {
this.logger.debug('No active worker thread to terminate');
cb();
}
}
}
//# sourceMappingURL=worker.js.map