UNPKG

redis-smq-common

Version:

RedisSMQ Common Library provides many components that are mainly used by RedisSMQ and RedisSMQ Monitor.

245 lines 10.2 kB
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