Skip to content
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: fixed socket destruction, timeout and leaking #2534

Closed
wants to merge 1 commit into from
Closed

http: fixed socket destruction, timeout and leaking #2534

wants to merge 1 commit into from

Conversation

tshemsedinov
Copy link
Contributor

  • Prevent 2 minutes sockets and memory hanging/leaking after server finished writing last response and connection hang inactive waiting for nothing (it is actual only in keep-alive mode, single HTTP requests does not hanging for 2 minutes, browsers do not reuse them and somehow do not close them too)
  • Fixed res._last, because it was not calculated correctly in this place before this PR and as a result socket.destroySoon() was never executed
  • Implemented server.keepAliveTimeout (default to 5 seconds) in addition to server.timeout
  • Prevent firing timeout event on server for finished and closed sockets
  • Removed socket.destroySoon() because it worked well on prefinish and have no sense on finish

Memory usage comparison before and after:
memory

Socket handlers usage comparison before and after:
sockets

Tests changed and provided new.
Related PR: #1411 by @indutny
Related Issue: nodejs/node-v0.x-archive#3460
Article about this problem (RU): http://habrahabr.ru/post/264851/

@@ -447,20 +448,25 @@ function connectionListener(socket) {
// if the user never called req.read(), and didn't pipe() or
// .resume() or .on('data'), then we call req._dump() so that the
// bytes will be pulled off the wire.
if (!req._consuming && !req._readableState.resumeScheduled)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary style fixes.

@mscdex mscdex added the http Issues or PRs related to the http subsystem. label Aug 25, 2015
@ChALkeR ChALkeR added the memory Issues and PRs related to the memory management or memory footprint. label Aug 25, 2015
@ChALkeR
Copy link
Member

ChALkeR commented Aug 25, 2015

This PR changes the expectations of at least one test without changing anything else in that test. Does it make this PR a semver-major, or would it still be semver-minor?

@tshemsedinov
Copy link
Contributor Author

This expectation https://github.com/tshemsedinov/io.js/blob/master/test/sequential/test-http-pipeline-flood.js#L51 based on wrong behavior, when timeout was emitted on sockets hanging open and on sockets already closed (it is actual just for keep-alive sockets).

// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this license boilerplate. The necessary licensing can already be found in the LICENSE file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

@Fishrock123
Copy link
Contributor

olecom added a commit to olecom/supro that referenced this pull request Aug 27, 2015
@ChALkeR
Copy link
Member

ChALkeR commented Sep 4, 2015


res.detachSocket(socket);
res._last = outgoing.length === 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because in this place _last is not calculated, so when length is 0, _last is not true and res will not processed as last.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is not calculated because the res might be not the last here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In debugging I found that when res was last in outgoing, and _last flag is not correct, but we need it calculated correctly for next lenes, to destroy socket if it is last.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, it should be a separate clause for this thing. _last indicates that there are not going to be any requests on the TCP socket. While there definitely could be in case of outgoing.length === 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We checking that on res:finish event, why it is not a right place to detect "is outgoing queue is empty?" In this place res._last inicates that we have no nore message to send.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we should destroy the socket immediately when the connection wasn't keep-alive, or when TCP connection has already ended.

@indutny
Copy link
Member

indutny commented Sep 9, 2015

@tshemsedinov : decided to do a proper write up, instead of in-line comments.

I like the idea of PR, but I think the way it interacts with existing logic could be potentially buggy. res._last is used to destroy the underlying socket when either:

  • incoming TCP connection has ended
  • request didn't have Connection: keep-alive

Instead of using it for different purpose, I suggest to do it without property it all. If there are no pending requests - let's call setTimeout with that keep alive timeout value. When the new request comes - setTimeout with regular timeout value should be called, to restore it.

Thanks for submitting this!

@tshemsedinov
Copy link
Contributor Author

Of course we can check outgoing queue and not changing _last immediately call setTimeout for socket destruction but I thought it was the purpose of this flag _last. I'll fix that, @indutny
What should I do with ditto marks? It is unwonted or indifferent?

@indutny
Copy link
Member

indutny commented Sep 10, 2015

@tshemsedinov there are lots of irrelevant style changes through the PR, ditto means "here too". Please change only the parts of code that are relevant to this behavior change. Also, there are some style issues (mostly 80 column limit).

@indutny
Copy link
Member

indutny commented Sep 10, 2015

Thanks!

@@ -482,8 +483,15 @@ function connectionListener(socket) {

res.detachSocket(socket);

if (res._last) {
socket.destroySoon();
if (res._last) socket.destroySoon();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found problem with non keep-alive sockets and here is fix for this case. @indutny, finally _last in it's untouched state is useful to detect non keep-alive sockets and destroy them as it was before this changes. Now all tests completed successfully.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

@ChALkeR
Copy link
Member

ChALkeR commented Sep 10, 2015

@indutny
Copy link
Member

indutny commented Sep 10, 2015

@ChALkeR : a bit premature? Maybe cancel?

@ChALkeR
Copy link
Member

ChALkeR commented Sep 10, 2015

@indutny He asked for it, why not? Most machines are free.

@aqrln
Copy link
Contributor

aqrln commented May 24, 2017

Wow, I think this wasn't a successful force-push :/
Why did GitHub close this?

@aqrln aqrln reopened this May 24, 2017
@aqrln
Copy link
Contributor

aqrln commented May 24, 2017

That was strange, but I've reopened the PR and the commit is here, great.

@aqrln
Copy link
Contributor

aqrln commented May 24, 2017

I wanted to trigger a fresh CI run on this, but something got really borked with this PR and git fetch upstream +refs/pull/2534/head:pr-2534 fetches master instead of this commit, both locally on my machine and on CI, so I had to kill the job.

node-test-commit CI job for tshemsedinov/io.js instead: https://ci.nodejs.org/job/node-test-commit/10108/
(I rebased the commit onto the latest master before force-pushing to @tshemsedinov's repo, so that should be fine.)

@aqrln
Copy link
Contributor

aqrln commented May 24, 2017

Yay, everything works fine after another force-push.

CI: https://ci.nodejs.org/job/node-test-pull-request/8282/

I've made some changes to the docs, namely: YAML header with the "added:" metadata, cross-links between the server.timeout and server.keepAliveTimeout sections, and a number of stylistic fixes. Can anyone review these, please? :)

/cc @jasnell @mcollina @fhinkel

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation generally LGTM.
Just a few nits and lite rephrasing

doc/api/http.md Outdated
@@ -841,8 +841,25 @@ Note that the socket timeout logic is set up on connection, so
changing this value only affects *new* connections to the server, not
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these two bulleted list notes?

Copy link
Contributor

@aqrln aqrln May 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not change it in this PR, it would be an unrelated stylistic change. But yeah, a nice thing to do in a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, never mind. I changed the server.timeout docs for consistency anyway.

doc/api/http.md Outdated
@@ -841,8 +841,25 @@ Note that the socket timeout logic is set up on connection, so
changing this value only affects *new* connections to the server, not
any existing connections.

Set to 0 to disable any kind of automatic timeout behavior on incoming
connections.
Set to 0 to disable timeout behavior on incoming connections.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] /s/Set to 0 to disable/A value of 0 will disable

P.S. what does it mean? Sockets will be destroyed ASAP, or timeout is delegated to OS?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latter option, roughly speaking. It will just disable any Node-specific timeout logic.

doc/api/http.md Outdated
Set to 0 to disable any kind of automatic timeout behavior on incoming
connections.
Set to 0 to disable timeout behavior on incoming connections.
If `server.timeout` is not 0 and [`server.keepAliveTimeout`][] is 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO you can simplify to just If [`server.keepAliveTimeout`][] is 0, the value of `server.timeout` will be used instead.
And now it's a Note: for server.keepAliveTimeout

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... or, maybe, even better to drop this sentence completely? It's not exactly accurate.

doc/api/http.md Outdated
added: REPLACEME
-->

* {Number} Defaults to 5000 (5 seconds).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* {Number} Timeout in milliseconds. Defaults to 5000.

doc/api/http.md Outdated

* {Number} Defaults to 5000 (5 seconds).

The number of milliseconds of inactivity after a server has finished writing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of milliseconds of inactivity the server needs to wait for additional incoming data, after it has finished writing the last response, before a socket will be destroyed.

doc/api/http.md Outdated
socket will be destroyed.

If the server receives new data before the keep-alive timeout has fired, it
will renew the regular inactivity timeout, i.e., [`server.timeout`][].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/s/renew/reset/

@aqrln
Copy link
Contributor

aqrln commented May 25, 2017

@refack thanks! I've made some amendments, can you please take another look?

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs LGTM.
Rubberstamp the code.

doc/api/http.md Outdated

The number of milliseconds of inactivity a server needs to wait for additional
incoming data, after it has finished writing the last response, before a socket
will be destroyed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit.
IMHO These two paragraphs could now be merged (the sentences have the same subject, so they could follow each other naturally)
very low importance on this

@aqrln
Copy link
Contributor

aqrln commented May 25, 2017

@jasnell as far as I see from the notifications, you're on GitHub now 😄 Can you please take a look at the latest changes while you are here? Thanks!

Implement server.keepAliveTimeout in addition to server.timeout to
prevent temporary socket/memory leaking in keep-alive mode.
@aqrln
Copy link
Contributor

aqrln commented May 25, 2017

Squashed and rebased onto master again.
Fresh CI: https://ci.nodejs.org/job/node-test-pull-request/8313/

I'd like to land it today or tomorrow.

/cc @jasnell @mcollina @fhinkel @refack

@refack
Copy link
Contributor

refack commented May 25, 2017

Copy link
Contributor

@refack refack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.
CITGM clean.

aqrln pushed a commit that referenced this pull request May 26, 2017
Implement server.keepAliveTimeout in addition to server.timeout to
prevent temporary socket/memory leaking in keep-alive mode.

PR-URL: #2534
Author: Timur Shemsedinov <timur.shemsedinov@gmail.com>
Author: Alexey Orlenko <eaglexrlnk@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
@aqrln
Copy link
Contributor

aqrln commented May 26, 2017

Landed in 0aa7ef5 🎉

@aqrln aqrln closed this May 26, 2017
jasnell pushed a commit that referenced this pull request May 28, 2017
Implement server.keepAliveTimeout in addition to server.timeout to
prevent temporary socket/memory leaking in keep-alive mode.

PR-URL: #2534
Author: Timur Shemsedinov <timur.shemsedinov@gmail.com>
Author: Alexey Orlenko <eaglexrlnk@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
@jasnell jasnell mentioned this pull request May 28, 2017
aqrln added a commit to aqrln/node that referenced this pull request Jun 12, 2017
Fix the logic of resetting the socket timeout of keep-alive HTTP
connections and add two tests:

* `test-http-server-keep-alive-timeout-slow-server` is a regression test
  for nodejsGH-13391.  It ensures that the server-side keep-alive timeout will
  not fire during processing of a request.

* `test-http-server-keep-alive-timeout-slow-headers` ensures that the
  regular socket timeout is restored as soon as a client starts sending
  a new request, not as soon as the whole message is received, so that
  the keep-alive timeout will not fire while, e.g., the client is
  sending large cookies.

Refs: nodejs#2534
Fixes: nodejs#13391
aqrln added a commit that referenced this pull request Jun 13, 2017
Fix the logic of resetting the socket timeout of keep-alive HTTP
connections and add two tests:

* `test-http-server-keep-alive-timeout-slow-server` is a regression test
  for GH-13391.  It ensures that the server-side keep-alive timeout will
  not fire during processing of a request.

* `test-http-server-keep-alive-timeout-slow-client-headers` ensures that
  the regular socket timeout is restored as soon as a client starts
  sending a new request, not as soon as the whole message is received,
  so that the keep-alive timeout will not fire while, e.g., the client
  is sending large cookies.

Refs: #2534
Fixes: #13391
PR-URL: #13549
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
MylesBorins pushed a commit that referenced this pull request Jun 13, 2017
Fix the logic of resetting the socket timeout of keep-alive HTTP
connections and add two tests:

* `test-http-server-keep-alive-timeout-slow-server` is a regression test
  for GH-13391.  It ensures that the server-side keep-alive timeout will
  not fire during processing of a request.

* `test-http-server-keep-alive-timeout-slow-client-headers` ensures that
  the regular socket timeout is restored as soon as a client starts
  sending a new request, not as soon as the whole message is received,
  so that the keep-alive timeout will not fire while, e.g., the client
  is sending large cookies.

Refs: #2534
Fixes: #13391
PR-URL: #13549
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
MylesBorins pushed a commit that referenced this pull request Jun 13, 2017
Fix the logic of resetting the socket timeout of keep-alive HTTP
connections and add two tests:

* `test-http-server-keep-alive-timeout-slow-server` is a regression test
  for GH-13391.  It ensures that the server-side keep-alive timeout will
  not fire during processing of a request.

* `test-http-server-keep-alive-timeout-slow-client-headers` ensures that
  the regular socket timeout is restored as soon as a client starts
  sending a new request, not as soon as the whole message is received,
  so that the keep-alive timeout will not fire while, e.g., the client
  is sending large cookies.

Refs: #2534
Fixes: #13391
PR-URL: #13549
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
@gibfahn
Copy link
Member

gibfahn commented Jan 15, 2018

Release team decided not to backport to v6.x. Comment if you would like it to be backported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
http Issues or PRs related to the http subsystem. memory Issues and PRs related to the memory management or memory footprint. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.