Skip to content

Commit

Permalink
fix: run GC before collecting open handles
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Apr 14, 2021
1 parent bc50e7f commit 74ed376
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -50,6 +50,7 @@
- `[jest-console]` `console.dir` now respects the second argument correctly ([#10638](https://github.com/facebook/jest/pull/10638))
- `[jest-core]` Don't report PerformanceObserver as open handle ([#11123](https://github.com/facebook/jest/pull/11123))
- `[jest-core]` Use `WeakRef` to hold timers when detecting open handles ([#11277](https://github.com/facebook/jest/pull/11277))
- `[jest-core]` Run GC detecting open handles ([#11278](https://github.com/facebook/jest/pull/11278))
- `[jest-each]` [**BREAKING**] Ignore excess words in headings ([#8766](https://github.com/facebook/jest/pull/8766))
- `[jest-environment]` [**BREAKING**] Drop support for `runScript` for test environments ([#11155](https://github.com/facebook/jest/pull/11155))
- `[jest-environment-jsdom]` Use inner realm’s `ArrayBuffer` constructor ([#10885](https://github.com/facebook/jest/pull/10885))
Expand Down
11 changes: 11 additions & 0 deletions e2e/__tests__/detectOpenHandles.ts
Expand Up @@ -71,6 +71,17 @@ it('does not report promises', () => {
expect(textAfterTest).toBe('');
});

it('does not report crypto random data', () => {
// The test here is basically that it exits cleanly without reporting anything (does not need `until`)
const {stderr} = runJest('detect-open-handles', [
'crypto',
'--detectOpenHandles',
]);
const textAfterTest = getTextAfterTest(stderr);

expect(textAfterTest).toBe('');
});

onNodeVersions('>=11.10.0', () => {
it('does not report ELD histograms', () => {
const {stderr} = runJest('detect-open-handles', [
Expand Down
13 changes: 13 additions & 0 deletions e2e/detect-open-handles/__tests__/crypto.js
@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const {randomFillSync} = require('crypto');

test('randomFillSync()', () => {
const buf = Buffer.alloc(10);
randomFillSync(buf);
});
29 changes: 27 additions & 2 deletions packages/jest-core/src/collectHandles.ts
Expand Up @@ -8,12 +8,15 @@
/* eslint-disable local/ban-types-eventually */

import * as asyncHooks from 'async_hooks';
import {promisify} from 'util';
import {setFlagsFromString} from 'v8';
import {runInNewContext} from 'vm';
import stripAnsi = require('strip-ansi');
import type {Config} from '@jest/types';
import {formatExecError} from 'jest-message-util';
import {ErrorWithStack} from 'jest-util';

export type HandleCollectionResult = () => Array<Error>;
export type HandleCollectionResult = () => Promise<Array<Error>>;

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

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

const tick = promisify(setImmediate);

function runGarbageCollector() {
const isGarbageCollectorHidden = !global.gc;

// GC is usually hidden, so we have to expose it before running.
setFlagsFromString('--expose-gc');
runInNewContext('gc')();

// The GC was not initially exposed, so let's hide it again.
if (isGarbageCollectorHidden) {
setFlagsFromString('--no-expose-gc');
}
}

// Inspired by https://github.com/mafintosh/why-is-node-running/blob/master/index.js
// Extracted as we want to format the result ourselves
export default function collectHandles(): HandleCollectionResult {
Expand Down Expand Up @@ -100,7 +118,14 @@ export default function collectHandles(): HandleCollectionResult {

hook.enable();

return () => {
return async () => {
runGarbageCollector();

// wait some ticks to allow GC to run properly, see https://github.com/nodejs/node/issues/34636#issuecomment-669366235
for (let i = 0; i < 10; i++) {
await tick();
}

hook.disable();

// Get errors for every async resource still referenced at this moment
Expand Down
6 changes: 3 additions & 3 deletions packages/jest-core/src/runJest.ts
Expand Up @@ -75,7 +75,7 @@ type ProcessResultOptions = Pick<
outputStream: NodeJS.WriteStream;
};

const processResults = (
const processResults = async (
runResults: AggregatedResult,
options: ProcessResultOptions,
) => {
Expand All @@ -89,7 +89,7 @@ const processResults = (
} = options;

if (collectHandles) {
runResults.openHandles = collectHandles();
runResults.openHandles = await collectHandles();
} else {
runResults.openHandles = [];
}
Expand Down Expand Up @@ -278,7 +278,7 @@ export default async function runJest({
await runGlobalHook({allTests, globalConfig, moduleName: 'globalTeardown'});
}

processResults(results, {
await processResults(results, {
collectHandles,
json: globalConfig.json,
onComplete,
Expand Down

0 comments on commit 74ed376

Please sign in to comment.