-
-
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
Wait for closed resources to actually close before detecting open handles #11429
Wait for closed resources to actually close before detecting open handles #11429
Conversation
Some types of async resources in Node.js are not destroyed until *after* their `close` or `end` or similar callbacks and events run, leading to a situation where the `--detectOpenHandles` option can falsely flag resources that have been cleaned up in user code and are already scheduled for destruction. For example, if a test ends from the callback to `TCPServer.close()` and no other tests or lifecycle functions impose additional delays, `--detectOpenHandles` will flag the server even though it has been closed. This is the main cause of issues people encounter with Supertest (see jestjs#8554). This addresses the issue by adding a short delay before collecting open handles. Depends on jestjs#11382.
This fixes a test that the previous commit broke. Some commands (for example, starting a TCP/HTTP server with the `host` option) can cause other async resources to be created indire ctly (so their stack traces don't have any user code in them). Since these are still triggered by things in a user's code, we now track and show them when `--detectOpenHandles` is used. This also increases the size of stack traces in `ErrorWithStack` because some of the Node internals in these async stack traces are deep enough that the test wrapper functions we lo ok for fall off the bottom of the stack trace!
Codecov Report
@@ Coverage Diff @@
## master #11429 +/- ##
==========================================
+ Coverage 69.26% 69.29% +0.02%
==========================================
Files 311 311
Lines 16273 16285 +12
Branches 4702 4707 +5
==========================================
+ Hits 11272 11285 +13
+ Misses 4973 4972 -1
Partials 28 28
Continue to review full report at Codecov.
|
This gist might be useful for playing with or exploring the indirectly created async resources: https://gist.github.com/Mr0grog/f6f3d97a234087ebf247b8faf73822b1 |
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 @Mr0grog, thanks! I can probably try myself if you will be busy for the next couple of days 🙂 |
Whoa, you cleaned this up and merged it in quick! Thanks. 😄 🙌 |
Thanks for the awesome PRs for this feature @Mr0grog! It's a really cool one that has had a couple of blemishes for a long time you've smoothed out 👍 This PR is included in |
Woohoo! Glad I could help improve things. 😄 |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
Note: this contains two major changes (waiting before detecting open handles, detecting indirectly opened handles), but they affect the same part of the code and one revealed the other as an issue, so it seemed simpler to combine them. I’ve made separate commits for each so it’s a little easier to see here, but am also happy to break them into separate PRs.
Some types of async resources in Node.js are not destroyed until after their
close
orend
or similar callbacks/events run, leading to a situation where the--detectOpenHandles
option can falsely flag resources that have been cleaned up in user code and are already scheduled for destruction. For example, if a test ends from the callback toTCPServer.close()
and no other tests or lifecycle functions impose additional delays,--detectOpenHandles
will flag the server as open even though it has been closed. This is also the main cause of issues people encounter with Supertest (see #8554).Example:
This addresses the issue by adding a short delay before collecting open handles. It’s similar to some work @SimenB implemented (and then unimplemented) here: 74ed376
Unfortunately, this got a little bit more complex because the delay caused a test to start failing, which revealed a new issue: It turns out that some arguments to
TCPServer.listen()
cause theTCPSERVERWRAP
async resource for the server to be created indirectly, where its stack trace has no reference to the user code that was ultimately responsible for it, which causes Jest not to track the resource.I’ve added tracking for those indirectly created resources here. When we track an indirectly created resource, we use the stack trace from the original call that triggered the indirect created it so the user can still see the call in their code that’s causing the problem.
For example, code like:
Leads to async resource creation/destruction like:
But
Leads to async resource creation/destruction like:
Note that the
TCPSERVERWRAP
stack does not haveuserCode
in it, and that theGETADDRINFOREQWRAP
resource (which does haveuserCode
in the stack) was destroyed.The changes here allow us to detect that
TCPSERVERWRAP
resource and associate it with the same line of user code as theGETADDRINFOREQWRAP
resource.I also increased the size of the stack trace that we check for entrypoints to user code. The V8 default is 10, which caused us not to track resources like the
DNSCHANNEL
above because it has more than 10 stack frames within node internals, causing the wrapper functions that mark this as user code to get cut off the end of the stack trace.Fixes #8554
Closes #9532
Test plan
This PR includes several unit and end-to-end tests that should cover the issues this is meant to solve.