Skip to content

Commit

Permalink
Improve detectOpenHandles (#13417)
Browse files Browse the repository at this point in the history
  • Loading branch information
liuxingbaoyu committed Sep 19, 2023
1 parent bd2305b commit 4727f80
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- `[@jest/core]` [**BREAKING**] Group together open handles with the same stack trace ([#13417](https://github.com/jestjs/jest/pull/13417), & [#14543](https://github.com/jestjs/jest/pull/14543))
- `[@jest/core, @jest/test-sequencer]` [**BREAKING**] Exposes `globalConfig` & `contexts` to `TestSequencer` ([#14535](https://github.com/jestjs/jest/pull/14535), & [#14543](https://github.com/jestjs/jest/pull/14543))
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade JSDOM to v22 ([#13825](https://github.com/jestjs/jest/pull/13825))
- `[@jest/fake-timers]` [**BREAKING**] Upgrade `@sinonjs/fake-timers` to v11 ([#14544](https://github.com/jestjs/jest/pull/14544))
Expand Down
2 changes: 1 addition & 1 deletion e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`prints message about flag on slow tests with a custom timeout 1`] = `
exports[`prints out info about open handlers 1`] = `
"Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
DNSCHANNEL,TCPSERVERWRAP
12 | const app = new Server();
13 |
Expand Down
11 changes: 8 additions & 3 deletions packages/jest-core/src/__tests__/collectHandles.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,28 @@ describe('collectHandles', () => {

it('should not collect the PerformanceObserver open handle', async () => {
const handleCollector = collectHandles();
const obs = new PerformanceObserver((list, observer) => {});

let obs = new PerformanceObserver((list, observer) => {});
obs.observe({entryTypes: ['mark']});
obs.disconnect();
obs = null;

const openHandles = await handleCollector();

expect(openHandles).not.toContainEqual(
expect.objectContaining({message: 'PerformanceObserver'}),
);
obs.disconnect();
});

it('should not collect the DNSCHANNEL open handle', async () => {
const handleCollector = collectHandles();

const resolver = new dns.Resolver();
let resolver = new dns.Resolver();
resolver.getServers();

// We must drop references to it
resolver = null;

const openHandles = await handleCollector();

expect(openHandles).not.toContainEqual(
Expand Down
84 changes: 45 additions & 39 deletions packages/jest-core/src/collectHandles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,7 @@ export default function collectHandles(): HandleCollectionResult {
// Skip resources that should not generally prevent the process from
// exiting, not last a meaningfully long time, or otherwise shouldn't be
// tracked.
if (
type === 'PROMISE' ||
type === 'TIMERWRAP' ||
type === 'ELDHISTOGRAM' ||
type === 'PerformanceObserver' ||
type === 'RANDOMBYTESREQUEST' ||
type === 'DNSCHANNEL' ||
type === 'ZLIB' ||
type === 'SIGNREQUEST'
) {
if (type === 'PROMISE') {
return;
}
const error = new ErrorWithStack(type, initHook, 100);
Expand Down Expand Up @@ -141,14 +132,18 @@ export default function collectHandles(): HandleCollectionResult {
// For example, Node.js TCP Servers are not destroyed until *after* their
// `close` callback runs. If someone finishes a test from the `close`
// callback, we will not yet have seen the resource be destroyed here.
await asyncSleep(100);
await asyncSleep(0);

if (activeHandles.size > 0) {
// For some special objects such as `TLSWRAP`.
// Ref: https://github.com/jestjs/jest/issues/11665
runGC();
await asyncSleep(30);

await asyncSleep(0);
if (activeHandles.size > 0) {
// For some special objects such as `TLSWRAP`.
// Ref: https://github.com/jestjs/jest/issues/11665
runGC();

await asyncSleep(0);
}
}

hook.disable();
Expand All @@ -167,33 +162,44 @@ export function formatHandleErrors(
errors: Array<Error>,
config: Config.ProjectConfig,
): Array<string> {
const stacks = new Set();

return (
errors
.map(err =>
formatExecError(err, config, {noStackTrace: false}, undefined, true),
)
// E.g. timeouts might give multiple traces to the same line of code
// This hairy filtering tries to remove entries with duplicate stack traces
.filter(handle => {
const ansiFree: string = stripAnsi(handle);

const match = ansiFree.match(/\s+at(.*)/);

if (!match || match.length < 2) {
return true;
}
const stacks = new Map<string, {stack: string; names: Set<string>}>();

for (const err of errors) {
const formatted = formatExecError(
err,
config,
{noStackTrace: false},
undefined,
true,
);

const stack = ansiFree.substr(ansiFree.indexOf(match[1])).trim();
// E.g. timeouts might give multiple traces to the same line of code
// This hairy filtering tries to remove entries with duplicate stack traces

if (stacks.has(stack)) {
return false;
}
const ansiFree: string = stripAnsi(formatted);
const match = ansiFree.match(/\s+at(.*)/);
if (!match || match.length < 2) {
continue;
}

stacks.add(stack);
const stackText = ansiFree.slice(ansiFree.indexOf(match[1])).trim();

const name = ansiFree.match(/(?<=● {2}).*$/m);
if (name == null || name.length === 0) {
continue;
}

const stack = stacks.get(stackText) || {
names: new Set(),
stack: formatted.replace(name[0], '%%OBJECT_NAME%%'),
};

stack.names.add(name[0]);

stacks.set(stackText, stack);
}

return true;
})
return Array.from(stacks.values()).map(({stack, names}) =>
stack.replace('%%OBJECT_NAME%%', Array.from(names).join(',')),
);
}

0 comments on commit 4727f80

Please sign in to comment.