worker.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /**
  2. * Copyright (c) 2017-present, Facebook, Inc. All rights reserved.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. *
  8. */
  9. 'use strict';
  10. Object.defineProperty(exports, '__esModule', {
  11. value: true
  12. });
  13. var _child_process;
  14. function _load_child_process() {
  15. return (_child_process = _interopRequireDefault(require('child_process')));
  16. }
  17. var _types;
  18. function _load_types() {
  19. return (_types = require('./types'));
  20. }
  21. function _interopRequireDefault(obj) {
  22. return obj && obj.__esModule ? obj : {default: obj};
  23. }
  24. /**
  25. * This class wraps the child process and provides a nice interface to
  26. * communicate with. It takes care of:
  27. *
  28. * - Re-spawning the process if it dies.
  29. * - Queues calls while the worker is busy.
  30. * - Re-sends the requests if the worker blew up.
  31. *
  32. * The reason for queueing them here (since childProcess.send also has an
  33. * internal queue) is because the worker could be doing asynchronous work, and
  34. * this would lead to the child process to read its receiving buffer and start a
  35. * second call. By queueing calls here, we don't send the next call to the
  36. * children until we receive the result of the previous one.
  37. *
  38. * As soon as a request starts to be processed by a worker, its "processed"
  39. * field is changed to "true", so that other workers which might encounter the
  40. * same call skip it.
  41. */
  42. exports.default = class {
  43. constructor(options) {
  44. this._options = options;
  45. this._queue = null;
  46. this._initialize();
  47. }
  48. getStdout() {
  49. return this._child.stdout;
  50. }
  51. getStderr() {
  52. return this._child.stderr;
  53. }
  54. send(request, onProcessStart, onProcessEnd) {
  55. const item = {next: null, onProcessEnd, onProcessStart, request};
  56. if (this._last) {
  57. this._last.next = item;
  58. } else {
  59. this._queue = item;
  60. }
  61. this._last = item;
  62. this._process();
  63. }
  64. _initialize() {
  65. const child = (_child_process || _load_child_process()).default.fork(
  66. require.resolve('./child'),
  67. // $FlowFixMe: Flow does not work well with Object.assign.
  68. Object.assign(
  69. {
  70. cwd: process.cwd(),
  71. env: Object.assign({}, process.env, {
  72. JEST_WORKER_ID: this._options.workerId
  73. }),
  74. // Suppress --debug / --inspect flags while preserving others (like --harmony).
  75. execArgv: process.execArgv.filter(v => !/^--(debug|inspect)/.test(v)),
  76. silent: true
  77. },
  78. this._options.forkOptions
  79. )
  80. );
  81. child.on('message', this._receive.bind(this));
  82. child.on('exit', this._exit.bind(this));
  83. // $FlowFixMe: wrong "ChildProcess.send" signature.
  84. child.send([
  85. (_types || _load_types()).CHILD_MESSAGE_INITIALIZE,
  86. false,
  87. this._options.workerPath
  88. ]);
  89. this._retries++;
  90. this._child = child;
  91. this._busy = false;
  92. // If we exceeded the amount of retries, we will emulate an error reply
  93. // coming from the child. This avoids code duplication related with cleaning
  94. // the queue, and scheduling the next call.
  95. if (this._retries > this._options.maxRetries) {
  96. const error = new Error('Call retries were exceeded');
  97. this._receive([
  98. (_types || _load_types()).PARENT_MESSAGE_ERROR,
  99. error.name,
  100. error.message,
  101. error.stack,
  102. {type: 'WorkerError'}
  103. ]);
  104. }
  105. }
  106. _process() {
  107. if (this._busy) {
  108. return;
  109. }
  110. let item = this._queue;
  111. // Calls in the queue might have already been processed by another worker,
  112. // so we have to skip them.
  113. while (item && item.request[1]) {
  114. item = item.next;
  115. }
  116. this._queue = item;
  117. if (item) {
  118. // Flag the call as processed, so that other workers know that they don't
  119. // have to process it as well.
  120. item.request[1] = true;
  121. // Tell the parent that this item is starting to be processed.
  122. item.onProcessStart(this);
  123. this._retries = 0;
  124. this._busy = true;
  125. // $FlowFixMe: wrong "ChildProcess.send" signature.
  126. this._child.send(item.request);
  127. } else {
  128. this._last = item;
  129. }
  130. }
  131. _receive(response /* Should be ParentMessage */) {
  132. const item = this._queue;
  133. if (!item) {
  134. throw new TypeError('Unexpected response with an empty queue');
  135. }
  136. const onProcessEnd = item.onProcessEnd;
  137. this._busy = false;
  138. this._process();
  139. switch (response[0]) {
  140. case (_types || _load_types()).PARENT_MESSAGE_OK:
  141. onProcessEnd(null, response[1]);
  142. break;
  143. case (_types || _load_types()).PARENT_MESSAGE_ERROR:
  144. let error = response[4];
  145. if (error != null && typeof error === 'object') {
  146. const extra = error;
  147. const NativeCtor = global[response[1]];
  148. const Ctor = typeof NativeCtor === 'function' ? NativeCtor : Error;
  149. error = new Ctor(response[2]);
  150. // $FlowFixMe: adding custom properties to errors.
  151. error.type = response[1];
  152. error.stack = response[3];
  153. for (const key in extra) {
  154. // $FlowFixMe: adding custom properties to errors.
  155. error[key] = extra[key];
  156. }
  157. }
  158. onProcessEnd(error, null);
  159. break;
  160. default:
  161. throw new TypeError('Unexpected response from worker: ' + response[0]);
  162. }
  163. }
  164. _exit(exitCode) {
  165. if (exitCode !== 0) {
  166. this._initialize();
  167. }
  168. }
  169. };