child.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Child = void 0;
  4. const child_process_1 = require("child_process");
  5. const net_1 = require("net");
  6. const worker_threads_1 = require("worker_threads");
  7. const enums_1 = require("../enums");
  8. const events_1 = require("events");
  9. /**
  10. * @see https://nodejs.org/api/process.html#process_exit_codes
  11. */
  12. const exitCodesErrors = {
  13. 1: 'Uncaught Fatal Exception',
  14. 2: 'Unused',
  15. 3: 'Internal JavaScript Parse Error',
  16. 4: 'Internal JavaScript Evaluation Failure',
  17. 5: 'Fatal Error',
  18. 6: 'Non-function Internal Exception Handler',
  19. 7: 'Internal Exception Handler Run-Time Failure',
  20. 8: 'Unused',
  21. 9: 'Invalid Argument',
  22. 10: 'Internal JavaScript Run-Time Failure',
  23. 12: 'Invalid Debug Argument',
  24. 13: 'Unfinished Top-Level Await',
  25. };
  26. /**
  27. * Child class
  28. *
  29. * This class is used to create a child process or worker thread, and allows using
  30. * isolated processes or threads for processing jobs.
  31. *
  32. */
  33. class Child extends events_1.EventEmitter {
  34. constructor(mainFile, processFile, opts = {
  35. useWorkerThreads: false,
  36. }) {
  37. super();
  38. this.mainFile = mainFile;
  39. this.processFile = processFile;
  40. this.opts = opts;
  41. this._exitCode = null;
  42. this._signalCode = null;
  43. this._killed = false;
  44. }
  45. get pid() {
  46. if (this.childProcess) {
  47. return this.childProcess.pid;
  48. }
  49. else if (this.worker) {
  50. // Worker threads pids can become negative when they are terminated
  51. // so we need to use the absolute value to index the retained object
  52. return Math.abs(this.worker.threadId);
  53. }
  54. else {
  55. throw new Error('No child process or worker thread');
  56. }
  57. }
  58. get exitCode() {
  59. return this._exitCode;
  60. }
  61. get signalCode() {
  62. return this._signalCode;
  63. }
  64. get killed() {
  65. if (this.childProcess) {
  66. return this.childProcess.killed;
  67. }
  68. return this._killed;
  69. }
  70. async init() {
  71. const execArgv = await convertExecArgv(process.execArgv);
  72. let parent;
  73. if (this.opts.useWorkerThreads) {
  74. this.worker = parent = new worker_threads_1.Worker(this.mainFile, Object.assign({ execArgv, stdin: true, stdout: true, stderr: true }, (this.opts.workerThreadsOptions
  75. ? this.opts.workerThreadsOptions
  76. : {})));
  77. }
  78. else {
  79. this.childProcess = parent = (0, child_process_1.fork)(this.mainFile, [], Object.assign({ execArgv, stdio: 'pipe' }, (this.opts.workerForkOptions ? this.opts.workerForkOptions : {})));
  80. }
  81. parent.on('exit', (exitCode, signalCode) => {
  82. this._exitCode = exitCode;
  83. // Coerce to null if undefined for backwards compatibility
  84. signalCode = typeof signalCode === 'undefined' ? null : signalCode;
  85. this._signalCode = signalCode;
  86. this._killed = true;
  87. this.emit('exit', exitCode, signalCode);
  88. // Clean all listeners, we do not expect any more events after "exit"
  89. parent.removeAllListeners();
  90. this.removeAllListeners();
  91. });
  92. parent.on('error', (...args) => this.emit('error', ...args));
  93. parent.on('message', (...args) => this.emit('message', ...args));
  94. parent.on('close', (...args) => this.emit('close', ...args));
  95. parent.stdout.pipe(process.stdout);
  96. parent.stderr.pipe(process.stderr);
  97. await this.initChild();
  98. }
  99. async send(msg) {
  100. return new Promise((resolve, reject) => {
  101. if (this.childProcess) {
  102. this.childProcess.send(msg, (err) => {
  103. if (err) {
  104. reject(err);
  105. }
  106. else {
  107. resolve();
  108. }
  109. });
  110. }
  111. else if (this.worker) {
  112. resolve(this.worker.postMessage(msg));
  113. }
  114. else {
  115. resolve();
  116. }
  117. });
  118. }
  119. killProcess(signal = 'SIGKILL') {
  120. if (this.childProcess) {
  121. this.childProcess.kill(signal);
  122. }
  123. else if (this.worker) {
  124. this.worker.terminate();
  125. }
  126. }
  127. async kill(signal = 'SIGKILL', timeoutMs) {
  128. if (this.hasProcessExited()) {
  129. return;
  130. }
  131. const onExit = onExitOnce(this.childProcess || this.worker);
  132. this.killProcess(signal);
  133. if (timeoutMs !== undefined && (timeoutMs === 0 || isFinite(timeoutMs))) {
  134. const timeoutHandle = setTimeout(() => {
  135. if (!this.hasProcessExited()) {
  136. this.killProcess('SIGKILL');
  137. }
  138. }, timeoutMs);
  139. await onExit;
  140. clearTimeout(timeoutHandle);
  141. }
  142. await onExit;
  143. }
  144. async initChild() {
  145. const onComplete = new Promise((resolve, reject) => {
  146. const onMessageHandler = (msg) => {
  147. if (!Object.values(enums_1.ParentCommand).includes(msg.cmd)) {
  148. return;
  149. }
  150. if (msg.cmd === enums_1.ParentCommand.InitCompleted) {
  151. resolve();
  152. }
  153. else if (msg.cmd === enums_1.ParentCommand.InitFailed) {
  154. const err = new Error();
  155. err.stack = msg.err.stack;
  156. err.message = msg.err.message;
  157. reject(err);
  158. }
  159. this.off('message', onMessageHandler);
  160. this.off('close', onCloseHandler);
  161. };
  162. const onCloseHandler = (code, signal) => {
  163. if (code > 128) {
  164. code -= 128;
  165. }
  166. const msg = exitCodesErrors[code] || `Unknown exit code ${code}`;
  167. reject(new Error(`Error initializing child: ${msg} and signal ${signal}`));
  168. this.off('message', onMessageHandler);
  169. this.off('close', onCloseHandler);
  170. };
  171. this.on('message', onMessageHandler);
  172. this.on('close', onCloseHandler);
  173. });
  174. await this.send({
  175. cmd: enums_1.ChildCommand.Init,
  176. value: this.processFile,
  177. });
  178. await onComplete;
  179. }
  180. hasProcessExited() {
  181. return !!(this.exitCode !== null || this.signalCode);
  182. }
  183. }
  184. exports.Child = Child;
  185. function onExitOnce(child) {
  186. return new Promise(resolve => {
  187. child.once('exit', () => resolve());
  188. });
  189. }
  190. const getFreePort = async () => {
  191. return new Promise(resolve => {
  192. const server = (0, net_1.createServer)();
  193. server.listen(0, () => {
  194. const { port } = server.address();
  195. server.close(() => resolve(port));
  196. });
  197. });
  198. };
  199. const convertExecArgv = async (execArgv) => {
  200. const standard = [];
  201. const convertedArgs = [];
  202. for (let i = 0; i < execArgv.length; i++) {
  203. const arg = execArgv[i];
  204. if (arg.indexOf('--inspect') === -1) {
  205. standard.push(arg);
  206. }
  207. else {
  208. const argName = arg.split('=')[0];
  209. const port = await getFreePort();
  210. convertedArgs.push(`${argName}=${port}`);
  211. }
  212. }
  213. return standard.concat(convertedArgs);
  214. };
  215. //# sourceMappingURL=child.js.map