import { ParentCommand } from '../enums'; import { errorToJSON } from '../utils'; var ChildStatus; (function (ChildStatus) { ChildStatus[ChildStatus["Idle"] = 0] = "Idle"; ChildStatus[ChildStatus["Started"] = 1] = "Started"; ChildStatus[ChildStatus["Terminating"] = 2] = "Terminating"; ChildStatus[ChildStatus["Errored"] = 3] = "Errored"; })(ChildStatus || (ChildStatus = {})); const RESPONSE_TIMEOUT = process.env.NODE_ENV === 'test' ? 500 : 5000; /** * ChildProcessor * * This class acts as the interface between a child process and it parent process * so that jobs can be processed in different processes. * */ export class ChildProcessor { constructor(send, receiver) { this.send = send; this.receiver = receiver; } async init(processorFile) { let processor; try { const { default: processorFn } = await import(processorFile); processor = processorFn; if (processor.default) { // support es2015 module. processor = processor.default; } if (typeof processor !== 'function') { throw new Error('No function is exported in processor file'); } } catch (err) { this.status = ChildStatus.Errored; return this.send({ cmd: ParentCommand.InitFailed, err: errorToJSON(err), }); } const origProcessor = processor; processor = function (job, token) { try { return Promise.resolve(origProcessor(job, token)); } catch (err) { return Promise.reject(err); } }; this.processor = processor; this.status = ChildStatus.Idle; await this.send({ cmd: ParentCommand.InitCompleted, }); } async start(jobJson, token) { if (this.status !== ChildStatus.Idle) { return this.send({ cmd: ParentCommand.Error, err: errorToJSON(new Error('cannot start a not idling child process')), }); } this.status = ChildStatus.Started; this.currentJobPromise = (async () => { try { const job = this.wrapJob(jobJson, this.send); const result = await this.processor(job, token); await this.send({ cmd: ParentCommand.Completed, value: typeof result === 'undefined' ? null : result, }); } catch (err) { await this.send({ cmd: ParentCommand.Failed, value: errorToJSON(!err.message ? new Error(err) : err), }); } finally { this.status = ChildStatus.Idle; this.currentJobPromise = undefined; } })(); } async stop() { } async waitForCurrentJobAndExit() { this.status = ChildStatus.Terminating; try { await this.currentJobPromise; } finally { process.exit(process.exitCode || 0); } } /** * Enhance the given job argument with some functions * that can be called from the sandboxed job processor. * * Note, the `job` argument is a JSON deserialized message * from the main node process to this forked child process, * the functions on the original job object are not in tact. * The wrapped job adds back some of those original functions. */ wrapJob(job, send) { const wrappedJob = Object.assign(Object.assign({}, job), { queueQualifiedName: job.queueQualifiedName, data: JSON.parse(job.data || '{}'), opts: job.opts, returnValue: JSON.parse(job.returnvalue || '{}'), /* * Proxy `updateProgress` function, should works as `progress` function. */ async updateProgress(progress) { // Locally store reference to new progress value // so that we can return it from this process synchronously. this.progress = progress; // Send message to update job progress. await send({ cmd: ParentCommand.Progress, value: progress, }); }, /* * Proxy job `log` function. */ log: async (row) => { await send({ cmd: ParentCommand.Log, value: row, }); }, /* * Proxy `moveToDelayed` function. */ moveToDelayed: async (timestamp, token) => { await send({ cmd: ParentCommand.MoveToDelayed, value: { timestamp, token }, }); }, /* * Proxy `moveToWait` function. */ moveToWait: async (token) => { await send({ cmd: ParentCommand.MoveToWait, value: { token }, }); }, /* * Proxy `moveToWaitingChildren` function. */ moveToWaitingChildren: async (token, opts) => { const requestId = Math.random().toString(36).substring(2, 15); await send({ requestId, cmd: ParentCommand.MoveToWaitingChildren, value: { token, opts }, }); return waitResponse(requestId, this.receiver, RESPONSE_TIMEOUT, 'moveToWaitingChildren'); }, /* * Proxy `updateData` function. */ updateData: async (data) => { await send({ cmd: ParentCommand.Update, value: data, }); wrappedJob.data = data; }, /** * Proxy `getChildrenValues` function. */ getChildrenValues: async () => { const requestId = Math.random().toString(36).substring(2, 15); await send({ requestId, cmd: ParentCommand.GetChildrenValues, }); return waitResponse(requestId, this.receiver, RESPONSE_TIMEOUT, 'getChildrenValues'); }, /** * Proxy `getIgnoredChildrenFailures` function. * * This method sends a request to retrieve the failures of ignored children * and waits for a response from the parent process. * * @returns - A promise that resolves with the ignored children failures. * The exact structure of the returned data depends on the parent process implementation. */ getIgnoredChildrenFailures: async () => { const requestId = Math.random().toString(36).substring(2, 15); await send({ requestId, cmd: ParentCommand.GetIgnoredChildrenFailures, }); return waitResponse(requestId, this.receiver, RESPONSE_TIMEOUT, 'getIgnoredChildrenFailures'); } }); return wrappedJob; } } const waitResponse = async (requestId, receiver, timeout, cmd) => { return new Promise((resolve, reject) => { const listener = (msg) => { if (msg.requestId === requestId) { resolve(msg.value); receiver.off('message', listener); } }; receiver.on('message', listener); setTimeout(() => { receiver.off('message', listener); reject(new Error(`TimeoutError: ${cmd} timed out in (${timeout}ms)`)); }, timeout); }); }; //# sourceMappingURL=child-processor.js.map