diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a227822b25b..fec971e3dc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Fixes +- `[jest-worker]` When a process runs out of memory worker exits correctly and doesn't spin indefinitely ([#13054](https://github.com/facebook/jest/pull/13054)) + ### Chore & Maintenance - `[*]` [**BREAKING**] Drop support for Node v12 and v17 ([#13033](https://github.com/facebook/jest/pull/13033)) diff --git a/packages/jest-worker/src/workers/ChildProcessWorker.ts b/packages/jest-worker/src/workers/ChildProcessWorker.ts index aa724124545b..9381310a30dc 100644 --- a/packages/jest-worker/src/workers/ChildProcessWorker.ts +++ b/packages/jest-worker/src/workers/ChildProcessWorker.ts @@ -206,7 +206,7 @@ export default class ChildProcessWorker implements WorkerInterface { } } - private _onExit(exitCode: number | null) { + private _onExit(exitCode: number | null, signal: NodeJS.Signals | null) { if ( exitCode !== 0 && exitCode !== null && @@ -219,6 +219,17 @@ export default class ChildProcessWorker implements WorkerInterface { this._child.send(this._request); } } else { + if (signal === 'SIGABRT') { + // When a child process worker crashes due to lack of memory this prevents + // jest from spinning and failing to exit. It could be argued it should restart + // the process, but if you're running out of memory then restarting processes + // is only going to make matters worse. + this._onProcessEnd( + new Error(`Process exited unexpectedly: ${signal}`), + null, + ); + } + this._shutdown(); } } diff --git a/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js b/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js index 1caa8e654380..6bcfcc0ff654 100644 --- a/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js +++ b/packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js @@ -375,6 +375,43 @@ it('restarts the child when the child process dies', () => { expect(childProcess.fork).toHaveBeenCalledTimes(2); }); +it('when out of memory occurs the worker is killed and exits', async () => { + const worker = new Worker({ + workerPath: '/tmp/foo', + }); + + expect(childProcess.fork).toHaveBeenCalledTimes(1); + + const onProcessStart = jest.fn(); + const onProcessEnd = jest.fn(); + const onCustomMessage = jest.fn(); + + worker.send( + [CHILD_MESSAGE_CALL, false, 'foo', []], + onProcessStart, + onProcessEnd, + onCustomMessage, + ); + + // Only onProcessStart has been called + expect(onProcessStart).toHaveBeenCalledTimes(1); + expect(onProcessEnd).not.toHaveBeenCalled(); + expect(onCustomMessage).not.toHaveBeenCalled(); + + forkInterface.emit('exit', null, 'SIGABRT'); + + // We don't want it to try and restart. + expect(childProcess.fork).toHaveBeenCalledTimes(1); + expect(onProcessEnd).toHaveBeenCalledTimes(1); + expect(onProcessEnd).toHaveBeenCalledWith( + new Error('Process exited unexpectedly: SIGABRT'), + null, + ); + + // It should not hang + await worker.waitForExit(); +}); + it('sends SIGTERM when forceExit() is called', async () => { const worker = new Worker({ forkOptions: {},