UNPKG

@shahadul-17/dispatcher

Version:

Defines a mechanism for parallel processing and CPU intensive tasks in Node.js

200 lines 10.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChildProcess = void 0; const child_process_1 = require("child_process"); const utilities_1 = require("@shahadul-17/utilities"); const event_manager_1 = require("@shahadul-17/event-manager"); const process_event_type_e_1 = require("./process-event-type.e"); const END_OF_DATA_MARKER = "<--- END OF DATA --->"; /** * Parent process shall use this class to communicate with the child process. */ class ChildProcess extends event_manager_1.EventManager { constructor(options) { super(); this._taskCount = 0; this._options = options; this.streamReader = new utilities_1.StreamReader(); this.streamReader.setLineDelimiter(END_OF_DATA_MARKER); // binding methods... this.incrementTaskCount = this.incrementTaskCount.bind(this); this.decrementTaskCount = this.decrementTaskCount.bind(this); this.onDataReceivedAsync = this.onDataReceivedAsync.bind(this); this.spawnAsync = this.spawnAsync.bind(this); this.sendAsync = this.sendAsync.bind(this); } get isChildProcess() { return false; } get taskCount() { return this._taskCount; } get processId() { return this.options.processId; } get options() { return this._options; } incrementTaskCount(step) { // if the provided step is not a positive number... if (!utilities_1.NumberUtilities.isPositiveNumber(step)) { // we shall set the step to 1... step = 1; } this._taskCount = this._taskCount + step; return this._taskCount; } decrementTaskCount(step) { // if the provided step is not a positive number... if (!utilities_1.NumberUtilities.isPositiveNumber(step)) { // we shall set the step to 1... step = 1; } this._taskCount = this._taskCount - step; // task count can never be less than zero... if (this._taskCount < 0) { this._taskCount = 0; } return this._taskCount; } onDataReceivedAsync(chunk) { return __awaiter(this, void 0, void 0, function* () { this.streamReader.append(chunk); let data; while (utilities_1.ObjectUtilities.isObject(data = this.streamReader.readObject())) { // dispatches data receive event... this.dispatchEventListeners({ type: process_event_type_e_1.ProcessEventType.DataReceive, data: data, }); } }); } spawnAsync() { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (utilities_1.StringUtilities.isUndefinedOrNullOrEmpty(this.options.processFileNameWithoutExtension)) { return reject(new Error("Child process file name without extension is not provided.")); } const childProcessFilePath = ChildProcess.toProcessFilePath(this.options.processFileNameWithoutExtension); const commandLineArguments = utilities_1.ObjectUtilities.isObject(this.options.commandLineArguments) ? this.options.commandLineArguments : utilities_1.ObjectUtilities.getEmptyObject(); const childProcessArguments = [ childProcessFilePath, "--isChildProcess", "true", "--processId", `${utilities_1.NumberUtilities.isNumber(this.processId) ? this.processId : -1}`, ]; for (const [key, value] of Object.entries(commandLineArguments)) { childProcessArguments.push(`--${key}`); childProcessArguments.push(`"${value}"`); } const childProcess = (0, child_process_1.spawn)("node", childProcessArguments, { shell: true, cwd: process.cwd(), env: { PATH: process.env.PATH, }, }); childProcess.on("spawn", () => __awaiter(this, void 0, void 0, function* () { // assigning child process... this.childProcess = childProcess; this.dispatchEventListeners({ type: process_event_type_e_1.ProcessEventType.Spawn, processId: this.processId, }); resolve(this); })); childProcess.on("disconnect", () => __awaiter(this, void 0, void 0, function* () { const error = new Error(`Child process with ID, '${this.processId}' has disconnected.`); const eventTypes = [ process_event_type_e_1.ProcessEventType.Disconnect, process_event_type_e_1.ProcessEventType.Error, ]; for (const eventType of eventTypes) { this.dispatchEventListeners({ type: eventType, processId: this.processId, error: error, }); } reject(error); })); childProcess.on("exit", (code, signal) => () => { const error = new Error(`Child process with ID, '${this.processId}' has exited with code '${code !== null && code !== void 0 ? code : ''}' and signal '${signal !== null && signal !== void 0 ? signal : ''}'.`); const eventTypes = [ process_event_type_e_1.ProcessEventType.Exit, process_event_type_e_1.ProcessEventType.Error, ]; for (const eventType of eventTypes) { this.dispatchEventListeners({ type: eventType, processId: this.processId, error: error, exitCode: utilities_1.NumberUtilities.isNumber(code) ? code : undefined, exitSignal: utilities_1.StringUtilities.isString(signal) ? signal : undefined, }); } reject(error); }); childProcess.on("close", (code, signal) => { const error = new Error(`Child process with ID, '${this.processId}' has closed with code '${code !== null && code !== void 0 ? code : ''}' and signal '${signal !== null && signal !== void 0 ? signal : ''}'.`); const eventTypes = [ process_event_type_e_1.ProcessEventType.Close, process_event_type_e_1.ProcessEventType.Error, ]; for (const eventType of eventTypes) { this.dispatchEventListeners({ type: eventType, processId: this.processId, error: error, exitCode: utilities_1.NumberUtilities.isNumber(code) ? code : undefined, exitSignal: utilities_1.StringUtilities.isString(signal) ? signal : undefined, }); } reject(error); }); childProcess.on("error", (error) => __awaiter(this, void 0, void 0, function* () { this.dispatchEventListeners({ type: process_event_type_e_1.ProcessEventType.Error, processId: this.processId, error: error, }); reject(error); })); // adding data receive listener... childProcess.stdout.setEncoding("utf-8"); childProcess.stdout.on("data", chunk => this.onDataReceivedAsync(chunk)); })); } sendAsync(data) { return __awaiter(this, void 0, void 0, function* () { // if the underlying child process has not spawned yet, // we wouldn't be able to send data to the child process... if (typeof this.childProcess === "undefined") { return false; } // any error thrown by this method shall be sent to the parent... let dataAsJson = utilities_1.JsonSerializer.serialize(data, { shallDeepSanitize: true, }); dataAsJson = `${dataAsJson}${END_OF_DATA_MARKER}\n`; // <-- we must add the new-line character... // we shall send data to the child process... this.childProcess.stdin.cork(); const isSent = this.childProcess.stdin.write(dataAsJson, "utf-8"); this.childProcess.stdin.uncork(); return isSent; }); } static toProcessFilePath(processFileNameWithoutExtension) { const currentFilePath = utilities_1.FileUtilities.toAbsolutePath(__filename); let currentDirectoryPath = utilities_1.FileUtilities.extractDirectoryPath(currentFilePath); // we want to go back by one directory... currentDirectoryPath = utilities_1.FileUtilities.extractDirectoryPath(currentDirectoryPath); const processFilePath = utilities_1.FileUtilities.join(currentDirectoryPath, `${processFileNameWithoutExtension}.js`); // we must enclose the file path with double quotes because the path might contain spaces... return `"${processFilePath}"`; } } exports.ChildProcess = ChildProcess; //# sourceMappingURL=child-process.js.map