-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
http: do not leak error listeners #43587
Conversation
Review requested:
|
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.
lgtm
b7f34df
to
56ba9ab
Compare
Maybe the commit message should instead be something like |
56ba9ab
to
275bb10
Compare
275bb10
to
f8244c0
Compare
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.
lgtm
Is this pattern considered reliable error-handling? I have used it in some application/script code, but it shouldn't be resilient enough for internal Node.js code. With this patch applied, check, for instance, the following test case const http = require('http');
function customErrorHandler(err) {
console.log(`Connection error handled: ${err.stack}`);
}
const app = (req, res) => {
res.socket.once('error', customErrorHandler);
res.end(JSON.stringify({
message: 'Hello World!'
}));
res.socket.emit('error', new Error(`something happened`)); // once() handler consumed
res.socket.emit('error', new Error(`something happened again :(`)); // Unhandled 'error' event
};
const httpServer = http.createServer(app);
httpServer.listen(80, () => {
console.log('HTTP Server running on port 80');
}); Hence, if the user applies some dynamic error handling, the app may eventually throw on an unhandled error event. |
Well, the thing is that in case of That said I anyway believe that the app should crash rather than leak memory as it will have an healthier runtime environment and will make error detection faster when monitoring it. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
d3d04fd
to
c4df0b5
Compare
c4df0b5
to
3bfee39
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This got lost in my notifications. But, the PR was released in Node.js v18.8.0. I just reran CITGM on Koa, and it's still failing on all platforms with the same error shown in #43587 (comment):
I'm guessing it's possible that Koa may just need their test updating as Express did (refs: #43587 (comment)) to account for this PR. |
I was just recently looking in to the Express.js tests and I think there is an actual issue here apart from updating the tests. There seems to be a bug introduced with this change were errors that were previously suppressed with the The change I mentioned in Express.js was just updating the error count to expect that the Node.js noop may not always be there, but it would seem weird that this change is now also causing unhandled exceptions to bubble up due to the lack of that noop handler. I asked above that why would it be desired to change the behavior to sometimes have the noop there and sometimes not (and leaving it with zero error listeners), but no one answered that question, so oh well. We had to add some error listeners in production apps due to new crashes for Node.js 18.9 due to this change, so again, not sure if that is intentional or not. We didn't have time to argue it more here since I already asked above and it wasn't answered and then landed anyway. I am only bring it up again due to the new post there with the same error. |
This reverts commit 736a7d8. The patch attempted to fix an issue, signaled by a warning, caused by an invalid API usage. However it introduced a behavior change that is breaking userland modules. It is a user error, so restore the original behavior and have the user investigate and fix the issue in their code. Refs: nodejs#43587 (comment) Refs: nodejs#43587 (comment)
@dougwilson I've opened a PR to revert this, but I fail to think of a scenario where this leads to an unhandled error, unless Even if an The process can crash if the user adds a one time listener for the |
I don't know the cause, but can add it. This is a production web app and nothing that I am aware of is manually emitting errors on the socket using But if you follow the code flow, an error emitted on the stocket will trigger the error listener added by Node.js and at the end, it calls That is how far I debugged the issue, and our solution was to add a You're welcome to not make any changes to Node.js, as we already implemented a work around to the problem. It was just a surprising change in behavior all of a sudden. I'm not trying to say how you should treat errors in node.js and what is or is not invalid api usage. I was just trying to point out a sudden behavior change in like a patch version that lead to a dos in our prod app and maybe help someone else out who has the same issue when upgrading. |
PR-URL: nodejs#43587 Fixes: nodejs#43548 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ricky Zhou <0x19951125@gmail.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Yes, but AFAIK a call to |
Then it sounds like maybe this uncovered a bug in that logic, as that is exactly what is happening. |
Maybe there is a reentrancy bug. It would be nice to have a test case. I'll try to write one when I have some time. |
This reverts commit 736a7d8. The patch attempted to fix an issue, signaled by a warning, caused by an invalid API usage. However it introduced a behavior change that is breaking userland modules. It is a user error, so restore the original behavior and have the user investigate and fix the issue in their code. Refs: #43587 (comment) Refs: #43587 (comment) PR-URL: #44921 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Beth Griggs <bethanyngriggs@gmail.com> Reviewed-By: Tierney Cyren <hello@bnb.im>
This reverts commit 736a7d8. The patch attempted to fix an issue, signaled by a warning, caused by an invalid API usage. However it introduced a behavior change that is breaking userland modules. It is a user error, so restore the original behavior and have the user investigate and fix the issue in their code. Refs: #43587 (comment) Refs: #43587 (comment) PR-URL: #44921 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Beth Griggs <bethanyngriggs@gmail.com> Reviewed-By: Tierney Cyren <hello@bnb.im>
This reverts commit 736a7d8. The patch attempted to fix an issue, signaled by a warning, caused by an invalid API usage. However it introduced a behavior change that is breaking userland modules. It is a user error, so restore the original behavior and have the user investigate and fix the issue in their code. Refs: #43587 (comment) Refs: #43587 (comment) PR-URL: #44921 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Beth Griggs <bethanyngriggs@gmail.com> Reviewed-By: Tierney Cyren <hello@bnb.im>
Hello! @lpinca Apparently it is a reentrancy bug on |
I found the problem. It's pretty unique and it was hidden so far from the memleak-leading behavior I fixed in this PR. A similar way to reproduce is by running this (even on current Node version) const { createConnection, createServer } = require('net')
const server = createServer()
server.listen(8000, () => {
const client = createConnection({ host: '127.0.0.1', port: 8000 })
function fn(err) {
console.log('HANDLED', err)
client.removeListener('error', fn)
client.destroy(err)
}
client.on('error', fn)
client.emit('error', new Error('ERROR'))
}) The problem with reentrancy when using express is that In our case, by following the flow it seems that when erroring As a solution to this, since I suspect there might be other edge cases likes this, I propose to keep the current |
That is not a reentrancy issue. The crash in that example is expected as per #43587 (comment). |
I agree on that. How do you see the solution I proposed? (Which I think might be useful for EE anyway) |
From my understanding the "leak" is a user error caused by people that keep the socket alive after the I'm not very happy to add an |
@ShogunPanda the snippet in #43587 (comment) is correct, and it should not be a problem. |
@mcollina @lpinca I think my example above was misleading. Now, I agree that we can improve the docs. But how do we both fix the memleak and NOT break express? |
My thoughts:
|
This PR prevents addition of a listener if not absolutely necessary, preventing a memory leak.
Fixes: #43548