1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', {
|
4 | value: true
|
5 | });
|
6 | exports.default = exports.SIGKILL_DELAY = void 0;
|
7 |
|
8 | function _child_process() {
|
9 | const data = require('child_process');
|
10 |
|
11 | _child_process = function () {
|
12 | return data;
|
13 | };
|
14 |
|
15 | return data;
|
16 | }
|
17 |
|
18 | function _os() {
|
19 | const data = require('os');
|
20 |
|
21 | _os = function () {
|
22 | return data;
|
23 | };
|
24 |
|
25 | return data;
|
26 | }
|
27 |
|
28 | function _mergeStream() {
|
29 | const data = _interopRequireDefault(require('merge-stream'));
|
30 |
|
31 | _mergeStream = function () {
|
32 | return data;
|
33 | };
|
34 |
|
35 | return data;
|
36 | }
|
37 |
|
38 | function _supportsColor() {
|
39 | const data = require('supports-color');
|
40 |
|
41 | _supportsColor = function () {
|
42 | return data;
|
43 | };
|
44 |
|
45 | return data;
|
46 | }
|
47 |
|
48 | var _types = require('../types');
|
49 |
|
50 | var _WorkerAbstract = _interopRequireDefault(require('./WorkerAbstract'));
|
51 |
|
52 | function _interopRequireDefault(obj) {
|
53 | return obj && obj.__esModule ? obj : {default: obj};
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | const SIGNAL_BASE_EXIT_CODE = 128;
|
63 | const SIGKILL_EXIT_CODE = SIGNAL_BASE_EXIT_CODE + 9;
|
64 | const SIGTERM_EXIT_CODE = SIGNAL_BASE_EXIT_CODE + 15;
|
65 |
|
66 | const SIGKILL_DELAY = 500;
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | exports.SIGKILL_DELAY = SIGKILL_DELAY;
|
87 |
|
88 | class ChildProcessWorker extends _WorkerAbstract.default {
|
89 | _child;
|
90 | _options;
|
91 | _request;
|
92 | _retries;
|
93 | _onProcessEnd;
|
94 | _onCustomMessage;
|
95 | _stdout;
|
96 | _stderr;
|
97 | _stderrBuffer = [];
|
98 | _memoryUsagePromise;
|
99 | _resolveMemoryUsage;
|
100 | _childIdleMemoryUsage;
|
101 | _childIdleMemoryUsageLimit;
|
102 | _memoryUsageCheck = false;
|
103 | _childWorkerPath;
|
104 |
|
105 | constructor(options) {
|
106 | super(options);
|
107 | this._options = options;
|
108 | this._request = null;
|
109 | this._stdout = null;
|
110 | this._stderr = null;
|
111 | this._childIdleMemoryUsage = null;
|
112 | this._childIdleMemoryUsageLimit = options.idleMemoryLimit || null;
|
113 | this._childWorkerPath =
|
114 | options.childWorkerPath || require.resolve('./processChild');
|
115 | this.state = _types.WorkerStates.STARTING;
|
116 | this.initialize();
|
117 | }
|
118 |
|
119 | initialize() {
|
120 | if (
|
121 | this.state === _types.WorkerStates.OUT_OF_MEMORY ||
|
122 | this.state === _types.WorkerStates.SHUTTING_DOWN ||
|
123 | this.state === _types.WorkerStates.SHUT_DOWN
|
124 | ) {
|
125 | return;
|
126 | }
|
127 |
|
128 | if (this._child && this._child.connected) {
|
129 | this._child.kill('SIGKILL');
|
130 | }
|
131 |
|
132 | this.state = _types.WorkerStates.STARTING;
|
133 | const forceColor = _supportsColor().stdout
|
134 | ? {
|
135 | FORCE_COLOR: '1'
|
136 | }
|
137 | : {};
|
138 | const silent = this._options.silent ?? true;
|
139 |
|
140 | if (!silent) {
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | console.warn('Unable to detect out of memory event if silent === false');
|
147 | }
|
148 |
|
149 | this._stderrBuffer = [];
|
150 | const options = {
|
151 | cwd: process.cwd(),
|
152 | env: {
|
153 | ...process.env,
|
154 | JEST_WORKER_ID: String(this._options.workerId + 1),
|
155 |
|
156 | ...forceColor
|
157 | },
|
158 |
|
159 | execArgv: process.execArgv.filter(v => !/^--(debug|inspect)/.test(v)),
|
160 |
|
161 | serialization: 'advanced',
|
162 | silent,
|
163 | ...this._options.forkOptions
|
164 | };
|
165 | this._child = (0, _child_process().fork)(
|
166 | this._childWorkerPath,
|
167 | [],
|
168 | options
|
169 | );
|
170 |
|
171 | if (this._child.stdout) {
|
172 | if (!this._stdout) {
|
173 |
|
174 |
|
175 | this._stdout = (0, _mergeStream().default)(this._getFakeStream());
|
176 | }
|
177 |
|
178 | this._stdout.add(this._child.stdout);
|
179 | }
|
180 |
|
181 | if (this._child.stderr) {
|
182 | if (!this._stderr) {
|
183 |
|
184 |
|
185 | this._stderr = (0, _mergeStream().default)(this._getFakeStream());
|
186 | }
|
187 |
|
188 | this._stderr.add(this._child.stderr);
|
189 |
|
190 | this._child.stderr.on('data', this.stderrDataHandler.bind(this));
|
191 | }
|
192 |
|
193 | this._child.on('message', this._onMessage.bind(this));
|
194 |
|
195 | this._child.on('exit', this._onExit.bind(this));
|
196 |
|
197 | this._child.on('disconnect', this._onDisconnect.bind(this));
|
198 |
|
199 | this._child.send([
|
200 | _types.CHILD_MESSAGE_INITIALIZE,
|
201 | false,
|
202 | this._options.workerPath,
|
203 | this._options.setupArgs
|
204 | ]);
|
205 |
|
206 | this._retries++;
|
207 |
|
208 |
|
209 |
|
210 | if (this._retries > this._options.maxRetries) {
|
211 | const error = new Error(
|
212 | `Jest worker encountered ${this._retries} child process exceptions, exceeding retry limit`
|
213 | );
|
214 |
|
215 | this._onMessage([
|
216 | _types.PARENT_MESSAGE_CLIENT_ERROR,
|
217 | error.name,
|
218 | error.message,
|
219 | error.stack,
|
220 | {
|
221 | type: 'WorkerError'
|
222 | }
|
223 | ]);
|
224 |
|
225 | this._request = null;
|
226 | }
|
227 |
|
228 | this.state = _types.WorkerStates.OK;
|
229 |
|
230 | if (this._resolveWorkerReady) {
|
231 | this._resolveWorkerReady();
|
232 | }
|
233 | }
|
234 |
|
235 | stderrDataHandler(chunk) {
|
236 | if (chunk) {
|
237 | this._stderrBuffer.push(Buffer.from(chunk));
|
238 | }
|
239 |
|
240 | this._detectOutOfMemoryCrash();
|
241 |
|
242 | if (this.state === _types.WorkerStates.OUT_OF_MEMORY) {
|
243 | this._workerReadyPromise = undefined;
|
244 | this._resolveWorkerReady = undefined;
|
245 | this.killChild();
|
246 |
|
247 | this._shutdown();
|
248 | }
|
249 | }
|
250 |
|
251 | _detectOutOfMemoryCrash() {
|
252 | try {
|
253 | const bufferStr = Buffer.concat(this._stderrBuffer).toString('utf8');
|
254 |
|
255 | if (
|
256 | bufferStr.includes('heap out of memory') ||
|
257 | bufferStr.includes('allocation failure;') ||
|
258 | bufferStr.includes('Last few GCs')
|
259 | ) {
|
260 | if (
|
261 | this.state === _types.WorkerStates.OK ||
|
262 | this.state === _types.WorkerStates.STARTING
|
263 | ) {
|
264 | this.state = _types.WorkerStates.OUT_OF_MEMORY;
|
265 | }
|
266 | }
|
267 | } catch (err) {
|
268 | console.error('Error looking for out of memory crash', err);
|
269 | }
|
270 | }
|
271 |
|
272 | _onDisconnect() {
|
273 | this._workerReadyPromise = undefined;
|
274 | this._resolveWorkerReady = undefined;
|
275 |
|
276 | this._detectOutOfMemoryCrash();
|
277 |
|
278 | if (this.state === _types.WorkerStates.OUT_OF_MEMORY) {
|
279 | this.killChild();
|
280 |
|
281 | this._shutdown();
|
282 | }
|
283 | }
|
284 |
|
285 | _onMessage(response) {
|
286 |
|
287 | let error;
|
288 |
|
289 | switch (response[0]) {
|
290 | case _types.PARENT_MESSAGE_OK:
|
291 | this._onProcessEnd(null, response[1]);
|
292 |
|
293 | break;
|
294 |
|
295 | case _types.PARENT_MESSAGE_CLIENT_ERROR:
|
296 | error = response[4];
|
297 |
|
298 | if (error != null && typeof error === 'object') {
|
299 | const extra = error;
|
300 |
|
301 | const NativeCtor = globalThis[response[1]];
|
302 | const Ctor = typeof NativeCtor === 'function' ? NativeCtor : Error;
|
303 | error = new Ctor(response[2]);
|
304 | error.type = response[1];
|
305 | error.stack = response[3];
|
306 |
|
307 | for (const key in extra) {
|
308 | error[key] = extra[key];
|
309 | }
|
310 | }
|
311 |
|
312 | this._onProcessEnd(error, null);
|
313 |
|
314 | break;
|
315 |
|
316 | case _types.PARENT_MESSAGE_SETUP_ERROR:
|
317 | error = new Error(`Error when calling setup: ${response[2]}`);
|
318 | error.type = response[1];
|
319 | error.stack = response[3];
|
320 |
|
321 | this._onProcessEnd(error, null);
|
322 |
|
323 | break;
|
324 |
|
325 | case _types.PARENT_MESSAGE_CUSTOM:
|
326 | this._onCustomMessage(response[1]);
|
327 |
|
328 | break;
|
329 |
|
330 | case _types.PARENT_MESSAGE_MEM_USAGE:
|
331 | this._childIdleMemoryUsage = response[1];
|
332 |
|
333 | if (this._resolveMemoryUsage) {
|
334 | this._resolveMemoryUsage(response[1]);
|
335 |
|
336 | this._resolveMemoryUsage = undefined;
|
337 | this._memoryUsagePromise = undefined;
|
338 | }
|
339 |
|
340 | this._performRestartIfRequired();
|
341 |
|
342 | break;
|
343 |
|
344 | default:
|
345 | throw new TypeError(`Unexpected response from worker: ${response[0]}`);
|
346 | }
|
347 | }
|
348 |
|
349 | _performRestartIfRequired() {
|
350 | if (this._memoryUsageCheck) {
|
351 | this._memoryUsageCheck = false;
|
352 | let limit = this._childIdleMemoryUsageLimit;
|
353 |
|
354 |
|
355 |
|
356 |
|
357 | if (limit && limit > 0 && limit <= 1) {
|
358 | limit = Math.floor((0, _os().totalmem)() * limit);
|
359 | } else if (limit) {
|
360 | limit = Math.floor(limit);
|
361 | }
|
362 |
|
363 | if (
|
364 | limit &&
|
365 | this._childIdleMemoryUsage &&
|
366 | this._childIdleMemoryUsage > limit
|
367 | ) {
|
368 | this.state = _types.WorkerStates.RESTARTING;
|
369 | this.killChild();
|
370 | }
|
371 | }
|
372 | }
|
373 |
|
374 | _onExit(exitCode) {
|
375 | this._workerReadyPromise = undefined;
|
376 | this._resolveWorkerReady = undefined;
|
377 |
|
378 | this._detectOutOfMemoryCrash();
|
379 |
|
380 | if (exitCode !== 0 && this.state === _types.WorkerStates.OUT_OF_MEMORY) {
|
381 | this._onProcessEnd(
|
382 | new Error('Jest worker ran out of memory and crashed'),
|
383 | null
|
384 | );
|
385 |
|
386 | this._shutdown();
|
387 | } else if (
|
388 | (exitCode !== 0 &&
|
389 | exitCode !== null &&
|
390 | exitCode !== SIGTERM_EXIT_CODE &&
|
391 | exitCode !== SIGKILL_EXIT_CODE &&
|
392 | this.state !== _types.WorkerStates.SHUTTING_DOWN) ||
|
393 | this.state === _types.WorkerStates.RESTARTING
|
394 | ) {
|
395 | this.state = _types.WorkerStates.RESTARTING;
|
396 | this.initialize();
|
397 |
|
398 | if (this._request) {
|
399 | this._child.send(this._request);
|
400 | }
|
401 | } else {
|
402 | this._shutdown();
|
403 | }
|
404 | }
|
405 |
|
406 | send(request, onProcessStart, onProcessEnd, onCustomMessage) {
|
407 | this._stderrBuffer = [];
|
408 | onProcessStart(this);
|
409 |
|
410 | this._onProcessEnd = (...args) => {
|
411 | const hasRequest = !!this._request;
|
412 |
|
413 |
|
414 | this._request = null;
|
415 |
|
416 | if (
|
417 | this._childIdleMemoryUsageLimit &&
|
418 | this._child.connected &&
|
419 | hasRequest
|
420 | ) {
|
421 | this.checkMemoryUsage();
|
422 | }
|
423 |
|
424 | return onProcessEnd(...args);
|
425 | };
|
426 |
|
427 | this._onCustomMessage = (...arg) => onCustomMessage(...arg);
|
428 |
|
429 | this._request = request;
|
430 | this._retries = 0;
|
431 |
|
432 | this._child.send(request, () => {});
|
433 | }
|
434 |
|
435 | waitForExit() {
|
436 | return this._exitPromise;
|
437 | }
|
438 |
|
439 | killChild() {
|
440 |
|
441 |
|
442 | const childToKill = this._child;
|
443 | childToKill.kill('SIGTERM');
|
444 | return setTimeout(() => childToKill.kill('SIGKILL'), SIGKILL_DELAY);
|
445 | }
|
446 |
|
447 | forceExit() {
|
448 | this.state = _types.WorkerStates.SHUTTING_DOWN;
|
449 | const sigkillTimeout = this.killChild();
|
450 |
|
451 | this._exitPromise.then(() => clearTimeout(sigkillTimeout));
|
452 | }
|
453 |
|
454 | getWorkerId() {
|
455 | return this._options.workerId;
|
456 | }
|
457 | |
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 | getWorkerSystemId() {
|
464 | return this._child.pid;
|
465 | }
|
466 |
|
467 | getStdout() {
|
468 | return this._stdout;
|
469 | }
|
470 |
|
471 | getStderr() {
|
472 | return this._stderr;
|
473 | }
|
474 | |
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 | getMemoryUsage() {
|
481 | if (!this._memoryUsagePromise) {
|
482 | let rejectCallback;
|
483 | const promise = new Promise((resolve, reject) => {
|
484 | this._resolveMemoryUsage = resolve;
|
485 | rejectCallback = reject;
|
486 | });
|
487 | this._memoryUsagePromise = promise;
|
488 |
|
489 | if (!this._child.connected && rejectCallback) {
|
490 | rejectCallback(new Error('Child process is not running.'));
|
491 | this._memoryUsagePromise = undefined;
|
492 | this._resolveMemoryUsage = undefined;
|
493 | return promise;
|
494 | }
|
495 |
|
496 | this._child.send([_types.CHILD_MESSAGE_MEM_USAGE], err => {
|
497 | if (err && rejectCallback) {
|
498 | this._memoryUsagePromise = undefined;
|
499 | this._resolveMemoryUsage = undefined;
|
500 | rejectCallback(err);
|
501 | }
|
502 | });
|
503 |
|
504 | return promise;
|
505 | }
|
506 |
|
507 | return this._memoryUsagePromise;
|
508 | }
|
509 | |
510 |
|
511 |
|
512 |
|
513 | checkMemoryUsage() {
|
514 | if (this._childIdleMemoryUsageLimit) {
|
515 | this._memoryUsageCheck = true;
|
516 |
|
517 | this._child.send([_types.CHILD_MESSAGE_MEM_USAGE], err => {
|
518 | if (err) {
|
519 | console.error('Unable to check memory usage', err);
|
520 | }
|
521 | });
|
522 | } else {
|
523 | console.warn(
|
524 | 'Memory usage of workers can only be checked if a limit is set'
|
525 | );
|
526 | }
|
527 | }
|
528 |
|
529 | isWorkerRunning() {
|
530 | return this._child.connected && !this._child.killed;
|
531 | }
|
532 | }
|
533 |
|
534 | exports.default = ChildProcessWorker;
|