-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
Improve detectOpenHandles
#13417
Improve detectOpenHandles
#13417
Changes from 1 commit
d1ab5c3
04fd4ba
5732328
319180c
751eec5
b0d7c51
f827305
7e3584d
c518fae
e569539
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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') { | ||
Comment on lines
85
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. include There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me try to destroy it. #13417 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Original issue with a reproduction: #11051 If node doesn't require a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can probably add const client = require("prom-client");
client.collectDefaultMetrics(); as an integration test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really sure what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's used to detect handles keeping the process running after tests complete (such as a rogue server, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. essentially to find out why https://jestjs.io/docs/cli#--forceexit would be needed |
||
return; | ||
} | ||
const error = new ErrorWithStack(type, initHook, 100); | ||
|
@@ -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/facebook/jest/issues/11665 | ||
runGC(); | ||
await asyncSleep(30); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking length is free, let's check it first. Another 100ms might be a bit long? I modified it to 30ms, you can also modify it to 0-10ms if you want, lol. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as low as possible while still passing all tests (without introducing flake) seems ideal 😀 |
||
|
||
await asyncSleep(0); | ||
if (activeHandles.size > 0) { | ||
// For some special objects such as `TLSWRAP`. | ||
// Ref: https://github.com/facebook/jest/issues/11665 | ||
runGC(); | ||
|
||
await asyncSleep(0); | ||
} | ||
} | ||
|
||
hook.disable(); | ||
|
@@ -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>}>(); | ||
|
||
errors.forEach(err => { | ||
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) { | ||
return; | ||
} | ||
|
||
stacks.add(stack); | ||
const stackText = ansiFree.substr(ansiFree.indexOf(match[1])).trim(); | ||
|
||
const name = ansiFree.match(/(?<=● {2}).*$/m); | ||
if (!name?.length) { | ||
return; | ||
} | ||
|
||
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(',')), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified the logic here and now it will report all types at the same stack.
Not sure if the prompt message needs to be modified, but I'm not good at English and can't do anything about it.🤦♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome change! We could probably land this on
main
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I can do this, but the tests in the main branch don't reflect this. I couldn't think of a test example like this for a while. (
DNSCHANNEL
is ignored in main branch)