| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.Child = void 0;
- const child_process_1 = require("child_process");
- const net_1 = require("net");
- const worker_threads_1 = require("worker_threads");
- 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) {
- // Worker threads pids can become negative when they are terminated
- // so we need to use the absolute value to index the retained object
- return Math.abs(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, Object.assign({ execArgv, stdin: true, stdout: true, stderr: true }, (this.opts.workerThreadsOptions
- ? this.opts.workerThreadsOptions
- : {})));
- }
- else {
- this.childProcess = parent = (0, child_process_1.fork)(this.mainFile, [], Object.assign({ execArgv, stdio: 'pipe' }, (this.opts.workerForkOptions ? this.opts.workerForkOptions : {})));
- }
- 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 (!Object.values(enums_1.ParentCommand).includes(msg.cmd)) {
- return;
- }
- 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
|