Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: when an out of memory event occurs process should exit correctly #13054

Merged
merged 2 commits into from Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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))
Expand Down
13 changes: 12 additions & 1 deletion packages/jest-worker/src/workers/ChildProcessWorker.ts
Expand Up @@ -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 &&
Expand All @@ -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();
}
}
Expand Down
Expand Up @@ -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: {},
Expand Down