Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

remove socket error handler after request is done #142

Closed
wants to merge 2 commits into from

Conversation

addaleax
Copy link
Contributor

Hi y’all! 😄 Carrying this over from nodejs/node#8279 (comment):

npm-registry-client uses request, which in turn uses an HTTP agent for reusing sockets, so the error handlers registered in these lines in npm-registry-client just pile up and keep being attached over the entire lifetime of the socket:

  req.on('socket', function (s) {
    s.on('error', cb)
  })

which leads to the famous Warning: Possible EventEmitter memory leak detected. 11 error listeners added. warning.

This patch seeks to fix this by removing the listener from the socket once the callback is invoked, as keeping it around after that would really just be a memory leak (… I can’t believe that warning message is right for once).

(btw, I’m still not sure having npm-registry-client attach error listeners on the socket itself is a correct thing to do, but given that it’s been that way since 2012 and this is a kind of high-profile package, I’m reluctant to do actual behavioural changes…)

@zkat
Copy link
Contributor

zkat commented Aug 27, 2016

This LGTM, although I'll ping @othiym23 or @iarna as a backup set of eyes because request is... quirky sometimes.

Although it's a relatively trivial patch, it might be pretty nice to have a test written for it so no one in the distant future decides that "well, the socket is closing, so this cb is unnecessary" :)

Thanks a lot for this. It'll be nice to stop having all that EventEmitter spam. 8D

@addaleax addaleax force-pushed the avoid-error-handlers-piling-up branch from 3012985 to 99628f5 Compare August 27, 2016 07:30
@addaleax
Copy link
Contributor Author

Although it's a relatively trivial patch, it might be pretty nice to have a test written for it so no one in the distant future decides that "well, the socket is closing, so this cb is unnecessary" :)

Thing is, the only observable change that could really be tested for here (I think) is that the warning pops up. I’ve added a check to test/lib/common.js that tests for no such warning being emitted… I’m not sure that’s what you had in mind but it makes the tests fail on master.

@zkat
Copy link
Contributor

zkat commented Aug 27, 2016

I was thinking of something along the lines of emitter.listenerCount() but it's true -- this is a pretty specific corner case test, but it still feels good to make sure something is there to guard against this leak in the future.

@addaleax
Copy link
Contributor Author

I was thinking of something along the lines of emitter.listenerCount()

Yeah … I’m just afraid it’s going to be tricky (and depending on implementation details?) to get to the emitter itself, and to get a reliable number of event listeners at which to cut off… if you think it’s worth it, I’ll do the digging and see if it’s possible.

@zkat
Copy link
Contributor

zkat commented Aug 27, 2016

I don't know if it's worth it either. I'll check back in on Monday to finalize this, but tbh it's probably fine without significant testing. :\

@othiym23
Copy link
Contributor

Is this solution, which feels a little hacky, just a consequence of using request with our own Agent implementation? I'm fine with the patch, given the EventEmitter leak that's showing up with the new version of request, but I'm a little paranoid that we're adding a temporary fix to a deeper problem, because it's not clear to me why request changed here in the first place.

Either way, I'm inclined to land this, maybe with a comment added to the code to make clear why this patch was added (and that it might need to be updated, changed, or removed) in the future.

@addaleax
Copy link
Contributor Author

Is this solution, which feels a little hacky, just a consequence of using request with our own Agent implementation?

Are you talking about the setup in lib/initialize.js? If so, the answer is very likely no – the behaviour of keeping user-added listeners when pulling a socket back into the agent’s pool is something that comes from Node core here.

but I'm a little paranoid that we're adding a temporary fix to a deeper problem, because it's not clear to me why request changed here in the first place.

That’s quite understandable, I don’t know why this popped up either (or just became more frequent? I think it’s rather that?).

Either way, I'm inclined to land this, maybe with a comment added to the code to make clear why this patch was added (and that it might need to be updated, changed, or removed) in the future.

Hmm, got anything concrete in mind? Otherwise I’d prefer doing some more digging (starting to bisect request right now) before I try add a comment that may or may not be accurate.

@othiym23
Copy link
Contributor

or just became more frequent? I think it’s rather that?

Nope, we got no reports of this prior to the latest request upgrade (see below for a link to a discussion of why).

Hmm, got anything concrete in mind? Otherwise I’d prefer doing some more digging (starting to bisect request right now) before I try add a comment that may or may not be accurate.

I think it's request/request@bf86f17, which, to be honest, feels like a pretty brute-force fix for request/request#1903. From reading the thread on request/request#1903, it sounds like it's sort of a historical accident that request was removing error handlers in the first place, and it won't be coming back, but I could be wrong.

`npm-registry-client` uses `request`, which in turn uses an HTTP agent
for reusing sockets, so the `error` handlers registered on it in
`npm-registry-client` just piled up and kept being attached over the
entire lifetime of the socket.

This patch seeks to fix this by removing the listener from the socket
once the callback is invoked, as keeping it around after that would
just be a memory leak.
@addaleax addaleax force-pushed the avoid-error-handlers-piling-up branch from 9c37a04 to dde0eb7 Compare August 29, 2016 18:19
@addaleax
Copy link
Contributor Author

I think it's request/request@bf86f17

Yup, I can confirm that… the removed response.connection.setMaxListeners(0) call seems like the reason for this.

If I’m understanding it correctly, the npm-registry-client shouldn’t need to do its own error handling for the socket here… I’ve added a comment that hints at that, and while it would match my first intuition and what the http logic in Node says, but I’d really prefer to err on the side of caution by keeping the semantics of this module as they are.

@isaacs
Copy link
Contributor

isaacs commented Aug 30, 2016

This fix is almost verbatim what I'd thought would be the approach when I posted #143.

It's probably safe to just remove the error handler, since socket errors are proxied to the request object now, and have been since (I think?) node v0.8, but I could be wrong and that should be investigated.

@othiym23
Copy link
Contributor

Since 0.10 is the oldest version of Node the npm CLI team currently supports, and we're sunsetting our support for 0.10 real soon now (along with the Node project), I'm fine with just landing this change. Thanks for the followup, @isaacs!

@mhart
Copy link

mhart commented Aug 31, 2016

I believe this should fix npm/npm#13656

@othiym23
Copy link
Contributor

othiym23 commented Sep 3, 2016

Landed as 0ff3352, df9be53, and f62a28d, and released as part of npm-release-client@7.2.0, which will be included in the next release of npm. Thanks so much for putting this together, @addaleax!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants