- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
216 lines
6.9 KiB
JavaScript
216 lines
6.9 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.Child = void 0;
|
|
const child_process_1 = require("child_process");
|
|
const worker_threads_1 = require("worker_threads");
|
|
const net_1 = require("net");
|
|
const enums_1 = require("../enums");
|
|
const events_1 = require("events");
|
|
/**
|
|
* @see https://nodejs.org/api/process.html#process_exit_codes
|
|
*/
|
|
const exitCodesErrors = {
|
|
1: 'Uncaught Fatal Exception',
|
|
2: 'Unused',
|
|
3: 'Internal JavaScript Parse Error',
|
|
4: 'Internal JavaScript Evaluation Failure',
|
|
5: 'Fatal Error',
|
|
6: 'Non-function Internal Exception Handler',
|
|
7: 'Internal Exception Handler Run-Time Failure',
|
|
8: 'Unused',
|
|
9: 'Invalid Argument',
|
|
10: 'Internal JavaScript Run-Time Failure',
|
|
12: 'Invalid Debug Argument',
|
|
13: 'Unfinished Top-Level Await',
|
|
};
|
|
/**
|
|
* Child class
|
|
*
|
|
* This class is used to create a child process or worker thread, and allows using
|
|
* isolated processes or threads for processing jobs.
|
|
*
|
|
*/
|
|
class Child extends events_1.EventEmitter {
|
|
constructor(mainFile, processFile, opts = {
|
|
useWorkerThreads: false,
|
|
}) {
|
|
super();
|
|
this.mainFile = mainFile;
|
|
this.processFile = processFile;
|
|
this.opts = opts;
|
|
this._exitCode = null;
|
|
this._signalCode = null;
|
|
this._killed = false;
|
|
}
|
|
get pid() {
|
|
if (this.childProcess) {
|
|
return this.childProcess.pid;
|
|
}
|
|
else if (this.worker) {
|
|
return this.worker.threadId;
|
|
}
|
|
else {
|
|
throw new Error('No child process or worker thread');
|
|
}
|
|
}
|
|
get exitCode() {
|
|
return this._exitCode;
|
|
}
|
|
get signalCode() {
|
|
return this._signalCode;
|
|
}
|
|
get killed() {
|
|
if (this.childProcess) {
|
|
return this.childProcess.killed;
|
|
}
|
|
return this._killed;
|
|
}
|
|
async init() {
|
|
const execArgv = await convertExecArgv(process.execArgv);
|
|
let parent;
|
|
if (this.opts.useWorkerThreads) {
|
|
this.worker = parent = new worker_threads_1.Worker(this.mainFile, {
|
|
execArgv,
|
|
stdin: true,
|
|
stdout: true,
|
|
stderr: true,
|
|
});
|
|
}
|
|
else {
|
|
this.childProcess = parent = (0, child_process_1.fork)(this.mainFile, [], {
|
|
execArgv,
|
|
stdio: 'pipe',
|
|
});
|
|
}
|
|
parent.on('exit', (exitCode, signalCode) => {
|
|
this._exitCode = exitCode;
|
|
// Coerce to null if undefined for backwards compatibility
|
|
signalCode = typeof signalCode === 'undefined' ? null : signalCode;
|
|
this._signalCode = signalCode;
|
|
this._killed = true;
|
|
this.emit('exit', exitCode, signalCode);
|
|
// Clean all listeners, we do not expect any more events after "exit"
|
|
parent.removeAllListeners();
|
|
this.removeAllListeners();
|
|
});
|
|
parent.on('error', (...args) => this.emit('error', ...args));
|
|
parent.on('message', (...args) => this.emit('message', ...args));
|
|
parent.on('close', (...args) => this.emit('close', ...args));
|
|
parent.stdout.pipe(process.stdout);
|
|
parent.stderr.pipe(process.stderr);
|
|
await this.initChild();
|
|
}
|
|
async send(msg) {
|
|
return new Promise((resolve, reject) => {
|
|
if (this.childProcess) {
|
|
this.childProcess.send(msg, (err) => {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
else {
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
else if (this.worker) {
|
|
resolve(this.worker.postMessage(msg));
|
|
}
|
|
else {
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
killProcess(signal = 'SIGKILL') {
|
|
if (this.childProcess) {
|
|
this.childProcess.kill(signal);
|
|
}
|
|
else if (this.worker) {
|
|
this.worker.terminate();
|
|
}
|
|
}
|
|
async kill(signal = 'SIGKILL', timeoutMs) {
|
|
if (this.hasProcessExited()) {
|
|
return;
|
|
}
|
|
const onExit = onExitOnce(this.childProcess || this.worker);
|
|
this.killProcess(signal);
|
|
if (timeoutMs !== undefined && (timeoutMs === 0 || isFinite(timeoutMs))) {
|
|
const timeoutHandle = setTimeout(() => {
|
|
if (!this.hasProcessExited()) {
|
|
this.killProcess('SIGKILL');
|
|
}
|
|
}, timeoutMs);
|
|
await onExit;
|
|
clearTimeout(timeoutHandle);
|
|
}
|
|
await onExit;
|
|
}
|
|
async initChild() {
|
|
const onComplete = new Promise((resolve, reject) => {
|
|
const onMessageHandler = (msg) => {
|
|
if (msg.cmd === enums_1.ParentCommand.InitCompleted) {
|
|
resolve();
|
|
}
|
|
else if (msg.cmd === enums_1.ParentCommand.InitFailed) {
|
|
const err = new Error();
|
|
err.stack = msg.err.stack;
|
|
err.message = msg.err.message;
|
|
reject(err);
|
|
}
|
|
this.off('message', onMessageHandler);
|
|
this.off('close', onCloseHandler);
|
|
};
|
|
const onCloseHandler = (code, signal) => {
|
|
if (code > 128) {
|
|
code -= 128;
|
|
}
|
|
const msg = exitCodesErrors[code] || `Unknown exit code ${code}`;
|
|
reject(new Error(`Error initializing child: ${msg} and signal ${signal}`));
|
|
this.off('message', onMessageHandler);
|
|
this.off('close', onCloseHandler);
|
|
};
|
|
this.on('message', onMessageHandler);
|
|
this.on('close', onCloseHandler);
|
|
});
|
|
await this.send({
|
|
cmd: enums_1.ChildCommand.Init,
|
|
value: this.processFile,
|
|
});
|
|
await onComplete;
|
|
}
|
|
hasProcessExited() {
|
|
return !!(this.exitCode !== null || this.signalCode);
|
|
}
|
|
}
|
|
exports.Child = Child;
|
|
function onExitOnce(child) {
|
|
return new Promise(resolve => {
|
|
child.once('exit', () => resolve());
|
|
});
|
|
}
|
|
const getFreePort = async () => {
|
|
return new Promise(resolve => {
|
|
const server = (0, net_1.createServer)();
|
|
server.listen(0, () => {
|
|
const { port } = server.address();
|
|
server.close(() => resolve(port));
|
|
});
|
|
});
|
|
};
|
|
const convertExecArgv = async (execArgv) => {
|
|
const standard = [];
|
|
const convertedArgs = [];
|
|
for (let i = 0; i < execArgv.length; i++) {
|
|
const arg = execArgv[i];
|
|
if (arg.indexOf('--inspect') === -1) {
|
|
standard.push(arg);
|
|
}
|
|
else {
|
|
const argName = arg.split('=')[0];
|
|
const port = await getFreePort();
|
|
convertedArgs.push(`${argName}=${port}`);
|
|
}
|
|
}
|
|
return standard.concat(convertedArgs);
|
|
};
|
|
//# sourceMappingURL=child.js.map
|