Skip to content

Commit 74ed376

Browse files
committed
fix: run GC before collecting open handles
1 parent bc50e7f commit 74ed376

File tree

5 files changed

+55
-5
lines changed

5 files changed

+55
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
- `[jest-console]` `console.dir` now respects the second argument correctly ([#10638](https://github.com/facebook/jest/pull/10638))
5151
- `[jest-core]` Don't report PerformanceObserver as open handle ([#11123](https://github.com/facebook/jest/pull/11123))
5252
- `[jest-core]` Use `WeakRef` to hold timers when detecting open handles ([#11277](https://github.com/facebook/jest/pull/11277))
53+
- `[jest-core]` Run GC detecting open handles ([#11278](https://github.com/facebook/jest/pull/11278))
5354
- `[jest-each]` [**BREAKING**] Ignore excess words in headings ([#8766](https://github.com/facebook/jest/pull/8766))
5455
- `[jest-environment]` [**BREAKING**] Drop support for `runScript` for test environments ([#11155](https://github.com/facebook/jest/pull/11155))
5556
- `[jest-environment-jsdom]` Use inner realm’s `ArrayBuffer` constructor ([#10885](https://github.com/facebook/jest/pull/10885))

e2e/__tests__/detectOpenHandles.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ it('does not report promises', () => {
7171
expect(textAfterTest).toBe('');
7272
});
7373

74+
it('does not report crypto random data', () => {
75+
// The test here is basically that it exits cleanly without reporting anything (does not need `until`)
76+
const {stderr} = runJest('detect-open-handles', [
77+
'crypto',
78+
'--detectOpenHandles',
79+
]);
80+
const textAfterTest = getTextAfterTest(stderr);
81+
82+
expect(textAfterTest).toBe('');
83+
});
84+
7485
onNodeVersions('>=11.10.0', () => {
7586
it('does not report ELD histograms', () => {
7687
const {stderr} = runJest('detect-open-handles', [
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. 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+
const {randomFillSync} = require('crypto');
9+
10+
test('randomFillSync()', () => {
11+
const buf = Buffer.alloc(10);
12+
randomFillSync(buf);
13+
});

packages/jest-core/src/collectHandles.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
/* eslint-disable local/ban-types-eventually */
99

1010
import * as asyncHooks from 'async_hooks';
11+
import {promisify} from 'util';
12+
import {setFlagsFromString} from 'v8';
13+
import {runInNewContext} from 'vm';
1114
import stripAnsi = require('strip-ansi');
1215
import type {Config} from '@jest/types';
1316
import {formatExecError} from 'jest-message-util';
1417
import {ErrorWithStack} from 'jest-util';
1518

16-
export type HandleCollectionResult = () => Array<Error>;
19+
export type HandleCollectionResult = () => Promise<Array<Error>>;
1720

1821
function stackIsFromUser(stack: string) {
1922
// Either the test file, or something required by it
@@ -41,6 +44,21 @@ const alwaysActive = () => true;
4144

4245
const hasWeakRef = typeof WeakRef === 'function';
4346

47+
const tick = promisify(setImmediate);
48+
49+
function runGarbageCollector() {
50+
const isGarbageCollectorHidden = !global.gc;
51+
52+
// GC is usually hidden, so we have to expose it before running.
53+
setFlagsFromString('--expose-gc');
54+
runInNewContext('gc')();
55+
56+
// The GC was not initially exposed, so let's hide it again.
57+
if (isGarbageCollectorHidden) {
58+
setFlagsFromString('--no-expose-gc');
59+
}
60+
}
61+
4462
// Inspired by https://github.com/mafintosh/why-is-node-running/blob/master/index.js
4563
// Extracted as we want to format the result ourselves
4664
export default function collectHandles(): HandleCollectionResult {
@@ -100,7 +118,14 @@ export default function collectHandles(): HandleCollectionResult {
100118

101119
hook.enable();
102120

103-
return () => {
121+
return async () => {
122+
runGarbageCollector();
123+
124+
// wait some ticks to allow GC to run properly, see https://github.com/nodejs/node/issues/34636#issuecomment-669366235
125+
for (let i = 0; i < 10; i++) {
126+
await tick();
127+
}
128+
104129
hook.disable();
105130

106131
// Get errors for every async resource still referenced at this moment

packages/jest-core/src/runJest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ type ProcessResultOptions = Pick<
7575
outputStream: NodeJS.WriteStream;
7676
};
7777

78-
const processResults = (
78+
const processResults = async (
7979
runResults: AggregatedResult,
8080
options: ProcessResultOptions,
8181
) => {
@@ -89,7 +89,7 @@ const processResults = (
8989
} = options;
9090

9191
if (collectHandles) {
92-
runResults.openHandles = collectHandles();
92+
runResults.openHandles = await collectHandles();
9393
} else {
9494
runResults.openHandles = [];
9595
}
@@ -278,7 +278,7 @@ export default async function runJest({
278278
await runGlobalHook({allTests, globalConfig, moduleName: 'globalTeardown'});
279279
}
280280

281-
processResults(results, {
281+
await processResults(results, {
282282
collectHandles,
283283
json: globalConfig.json,
284284
onComplete,

0 commit comments

Comments
 (0)