Skip to content

Commit

Permalink
Eliminate Farm work results leakage by clearing callback reference (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
IIIEII committed Feb 25, 2022
1 parent 3a411d1 commit fc3d034
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -53,6 +53,7 @@
- `[jest-matcher-utils]` Pass maxWidth to `pretty-format` to avoid printing every element in arrays by default ([#12402](https://github.com/facebook/jest/pull/12402))
- `[jest-mock]` Fix function overloads for `spyOn` to allow more correct type inference in complex object ([#12442](https://github.com/facebook/jest/pull/12442))
- `[jest-reporters]` Notifications generated by the `--notify` flag are no longer persistent in GNOME Shell. ([#11733](https://github.com/facebook/jest/pull/11733))
- `[jest-worker]` Fix `Farm` execution results memory leak ([#12497](https://github.com/facebook/jest/pull/12497))

### Chore & Maintenance

Expand Down
7 changes: 5 additions & 2 deletions packages/jest-worker/src/Farm.ts
Expand Up @@ -127,9 +127,12 @@ export default class Farm {
// Reference the task object outside so it won't be retained by onEnd,
// and other properties of the task object, such as task.request can be
// garbage collected.
const taskOnEnd = task.onEnd;
let taskOnEnd: OnEnd | null = task.onEnd;
const onEnd: OnEnd = (error, result) => {
taskOnEnd(error, result);
if (taskOnEnd) {
taskOnEnd(error, result);
}
taskOnEnd = null;

this._unlock(workerId);
this._process(workerId);
Expand Down
81 changes: 61 additions & 20 deletions packages/jest-worker/src/__tests__/leak-integration.test.ts
Expand Up @@ -11,30 +11,71 @@ import {writeFileSync} from 'graceful-fs';
import LeakDetector from 'jest-leak-detector';
import {Worker} from '../../build/index';

let workerFile!: string;
beforeAll(() => {
workerFile = join(tmpdir(), 'baz.js');
writeFileSync(workerFile, 'module.exports.fn = () => {};');
});
describe('WorkerThreads leaks', () => {
let workerFile!: string;
beforeAll(() => {
workerFile = join(tmpdir(), 'baz.js');
writeFileSync(workerFile, 'module.exports.fn = () => {};');
});

let worker!: Worker;
beforeEach(() => {
worker = new Worker(workerFile, {
enableWorkerThreads: true,
exposedMethods: ['fn'],
let worker!: Worker;
beforeEach(() => {
worker = new Worker(workerFile, {
enableWorkerThreads: true,
exposedMethods: ['fn'],
});
});
afterEach(async () => {
await worker.end();
});

it('does not retain arguments after a task finished', async () => {
let leakDetector!: LeakDetector;
await new Promise((resolve, reject) => {
const obj = {};
leakDetector = new LeakDetector(obj);
(worker as any).fn(obj).then(resolve, reject);
});

expect(await leakDetector.isLeaking()).toBe(false);
});
});
afterEach(async () => {
await worker.end();
});

it('does not retain arguments after a task finished', async () => {
let leakDetector!: LeakDetector;
await new Promise((resolve, reject) => {
const obj = {};
leakDetector = new LeakDetector(obj);
(worker as any).fn(obj).then(resolve, reject);
describe('Worker leaks', () => {
let workerFile!: string;
beforeAll(() => {
workerFile = join(tmpdir(), 'baz.js');
writeFileSync(workerFile, 'module.exports.fn = (obj) => [obj];');
});

let worker!: Worker;
beforeEach(() => {
worker = new Worker(workerFile, {
enableWorkerThreads: false,
exposedMethods: ['fn'],
});
});
afterEach(async () => {
await worker.end();
});

it('does not retain result after next task call', async () => {
let leakDetector!: LeakDetector;
await new Promise((resolve, reject) => {
const obj = {};
(worker as any)
.fn(obj)
.then((result: unknown) => {
leakDetector = new LeakDetector(result);
return result;
})
.then(resolve, reject);
});
await new Promise((resolve, reject) => {
const obj = {};
(worker as any).fn(obj).then(resolve, reject);
});

expect(await leakDetector.isLeaking()).toBe(false);
expect(await leakDetector.isLeaking()).toBe(false);
});
});

0 comments on commit fc3d034

Please sign in to comment.