@shahadul-17/dispatcher
Version:
Defines a mechanism for parallel processing and CPU intensive tasks in Node.js
200 lines • 10.1 kB
JavaScript
"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