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

net/http2: use actual Timeout instances #17704

Merged
merged 4 commits into from
Dec 20, 2017

Conversation

Fishrock123
Copy link
Contributor

@Fishrock123 Fishrock123 commented Dec 16, 2017

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

timers, net


This PR is a re-made version of #11154

This makes net.Sockets, Http2Streams, and Http2Sessions use actual Timeout objects in a [kTimeout] symbol property, rather than making the socket/stream/session itself a timer and appending properties to it directly.

This should make the code generally easier to understand, and might also prevent some deopts from properties being changes on the socket itself.

It is possible this could minorly effect performance either better or worse, but benchmarks on net.Socket from #11154 showed little difference.

This also moves some of the Timeout prototype into the internal file but not all of it, perhaps we can do that in a later commit if desirable.

New: Also exposes a timer duration validation function, using enroll()'s validation logic.


Unfortunately, sometime in the past few months some HTTP change occured that I haven't been able to get past, so two tests fail. 😕
This change somehow causes a clientError to be emitted in both test-http-server-keep-alive-timeout-slow-client-headers and test-http-server-keep-alive-timeout-slow-server... I added the extra temporary patch to help debug those. It seems to fail to parse the request chunk... somehow. Hopefully @nodejs/http can help...

Test failure output in the fold
=== release test-http-server-keep-alive-timeout-slow-client-headers ===
Path: sequential/test-http-server-keep-alive-timeout-slow-client-headers
'HTTP/1.1 200 OK\r\nDate: Fri, 15 Dec 2017 23:34:02 GMT\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n'
{ Error: Parse Error
    at socketOnEnd (_http_server.js:424:20)
    at Socket.emit (events.js:132:15)
    at endReadableNT (_stream_readable.js:1062:12)
    at process._tickCallback (internal/process/next_tick.js:152:19) bytesParsed: 0, code: 'HPE_INVALID_EOF_STATE' }
events.js:112
      throw er; // Unhandled 'error' event
      ^

Error [ERR_STREAM_WRITE_AFTER_END]: write after end
    at writeAfterEnd (_stream_writable.js:237:12)
    at Socket.Writable.write (_stream_writable.js:289:5)
    at Socket.write (net.js:740:40)
pick a07f487c98 net: use actual Timeout instance on Sockets
    at Timeout.setTimeout [as _onTimeout] (/Users/Jeremiah/Documents/node/test/sequential/test-http-server-keep-alive-timeout-slow-client-headers.js:41:14)
    at ontimeout (timers.js:475:11)
    at tryOnTimeout (timers.js:308:5)
    at Timer.listOnTimeout (timers.js:268:5)
Command: out/Release/node /Users/Jeremiah/Documents/node/test/sequential/test-http-server-keep-alive-timeout-slow-client-headers.js
=== release test-http-server-keep-alive-timeout-slow-server ===
Path: sequential/test-http-server-keep-alive-timeout-slow-server
events.js:112
      throw er; // Unhandled 'error' event
      ^

Error: socket hang up
    at createHangUpError (_http_client.js:298:15)
    at Socket.socketOnEnd (_http_client.js:391:23)
    at Socket.emit (events.js:132:15)
    at endReadableNT (_stream_readable.js:1062:12)
    at process._tickCallback (internal/process/next_tick.js:152:19)
Command: out/Release/node /Users/Jeremiah/Documents/node/test/sequential/test-http-server-keep-alive-timeout-slow-server.js

CI (going to fail): https://ci.nodejs.org/job/node-test-pull-request/12145/

@Fishrock123 Fishrock123 added help wanted Issues that need assistance from volunteers or PRs that need help to proceed. http Issues or PRs related to the http subsystem. net Issues and PRs related to the net subsystem. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. labels Dec 16, 2017
@Fishrock123 Fishrock123 added this to the 10.0.0 milestone Dec 16, 2017
@nodejs-github-bot nodejs-github-bot added build Issues and PRs related to build files or the CI. net Issues and PRs related to the net subsystem. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. labels Dec 16, 2017
@Fishrock123 Fishrock123 removed the build Issues and PRs related to build files or the CI. label Dec 16, 2017
@Fishrock123
Copy link
Contributor Author

Fishrock123 commented Dec 16, 2017

Ok so it looks like the @nodejs/http team is somewhat outdated so... maybe let's try @nodejs/http2 and see if anyone there has an idea of what's up. 🙃

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

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

net module, timeouts, a broken test … sounds like the perfect thing for @apapirovski to take a look at 🙃

I’ll try to look at the failure myself though.

@@ -381,14 +387,36 @@ Socket.prototype.read = function(n) {
};

Socket.prototype.setTimeout = function(msecs, callback) {
// Type checking identical to timers.enroll()
Copy link
Member

Choose a reason for hiding this comment

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

This might be enough code to factor it out into some internal function, wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be a good idea yeah.

@dougwilson
Copy link
Member

I think this PR may break the "mysql" module. I want to run the test suite, but not sure on the process. I assume this change won't be in a prebuilt nightly until it is merged, is that right?

@Fishrock123
Copy link
Contributor Author

Fishrock123 commented Dec 16, 2017

I assume this change won't be in a prebuilt nightly until it is merged, is that right?

Correct. You'd need to build your own version of node probably. I could do an "RC" with it technically but I'm not super familiar with that process.

@dougwilson Is "mysql" in citgm?

@dougwilson
Copy link
Member

Is "mysql" in citgm?

I don't see it listed. Not sure if it qualifies to be in there or not. I've looked around through the changes and how mysql is using the timers and I don't think it will be an issue. I was thinking it hooked into the socket timers but I think it is using the timers on it's own objects. I'll build a copy, no worries 👍

@Fishrock123
Copy link
Contributor Author

Ok, extracted the type checking. Not quite sure how that commit triggered it but it seems to have exposed that I also need to update the patch to include HTTP/2...

@addaleax
Copy link
Member

Here’s a citgm mysql run: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/1156/

There do appear to be some errors related to domains/async tracking in there…

@Fishrock123
Copy link
Contributor Author

@addaleax It appears that already occurs on master: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/1157/

@apapirovski
Copy link
Member

To be fair, that test is pretty brittle...

assert.equal(domain.active, null, 'query is not bound to domain');

Not sure we have any responsibility there.

@dougwilson
Copy link
Member

I've never used domains and am not familiar. Domain support was contributed to the module a long time ago. What should the test be instead?

@apapirovski
Copy link
Member

@dougwilson I'm looking into this more, after running it a few times, I don't think it's related to what I had initially thought. Will update when I have more info.

@Fishrock123
Copy link
Contributor Author

Added an http2 patch. I REALLY wish manual timers were never used there. We should probably just ban using enroll(), honestly.

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

if (msecs === 0) {
unenroll(this);
clearTimeout(this[kTimeout]);

if (callback !== undefined) {
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 feel like these conditionals are probably not how the API should work, but I'll leave that for another PR.

this[kTimeout] = setUnrefTimeout(() => {
this._onTimeout();
}, msecs);
if (this[kSession]) this[kSession][kUpdateTimer]();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems messy but it doesn't seem like [kUpdateTimer] should create new timers? 😕

Copy link
Contributor Author

@Fishrock123 Fishrock123 Dec 17, 2017

Choose a reason for hiding this comment

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

Maybe this is actually a bug? @jasnell should HTTP2Stream#setTimeout() create a new timeout on the session, if there is a session?

Copy link
Member

Choose a reason for hiding this comment

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

@Fishrock123 that line is to differentiate between HTTP2Stream and HTTP2Session, as this code is attached to both prototypes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Understood, but why refresh the session timer from the stream with it's existing timeout? Is that really intended from here without updating the session's timeout duration to a potential new duration?

Copy link
Member

Choose a reason for hiding this comment

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

The session timeout needs to be refreshed every time any of the associated streams moves data, as the socket is there.

@Fishrock123 Fishrock123 changed the title net: use actual Timeout instances on Sockets net/http2: use actual Timeout instances Dec 17, 2017
@Fishrock123 Fishrock123 added the http2 Issues or PRs related to the http2 subsystem. label Dec 17, 2017
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

LGTM

@Fishrock123 Fishrock123 force-pushed the use-timeout-in-net-socket branch from dbfcce7 to c396b24 Compare December 18, 2017 16:14
@Fishrock123
Copy link
Contributor Author

OK, I fixed the http tests issue. Turns out I was leaking timeouts but that should be fixed now by clearing an existing timeout before creating a new one.

Copy link
Member

@apapirovski apapirovski left a comment

Choose a reason for hiding this comment

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

This is great! 👍

this[kUpdateTimer]();
clearTimeout(this[kTimeout]);
this[kTimeout] = setUnrefTimeout(() => {
this._onTimeout();
Copy link
Member

Choose a reason for hiding this comment

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

This won't be feasible for http but for http2, I think we could move this off the publicly available _onTimeout and instead onto a Symbol.

Also, I would prefer if this was bound instead of using an arrow function but that's just a minor nit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Prefer that to be done in a separate commit. I'll switch to bind(), IIRC perf was good enough now?

Copy link
Member

Choose a reason for hiding this comment

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

Prefer that to be done in a separate commit.

I figured it could go here since this PR is what enables us to no longer use a publicly exposed method but either way works for me. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, true. Not sure how I feel about it.

lib/net.js Outdated
if (msecs === 0) {
timers.unenroll(this);
clearTimeout(this[kTimeout]);
Copy link
Member

Choose a reason for hiding this comment

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

Can we just move this outside of the condition since it's in both branches?

this[kTimeout] = setUnrefTimeout(() => {
this._onTimeout();
}, msecs);
if (this[kSession]) this[kSession][kUpdateTimer]();
Copy link
Member

Choose a reason for hiding this comment

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

Does this actually need the conditional? Couldn't it just call this[kUpdateTimer](); like before?

Copy link
Member

Choose a reason for hiding this comment

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

that if (this[kSession]) is roughly equal to if (this instanceof HTTP2Stream)

Copy link
Member

Choose a reason for hiding this comment

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

It just seems like we could just straight up call this[kUpdateTimer]() since it has the same check in it. If this is about avoiding the _unrefActive then this call could just be moved before this[kTimeout] is declared.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What if there is no session then?

There is no point in calling this[kUpdateTimer](); on the Stream - you'll just insert the stream timer twice... Might as well move the extra conditional bit here.

Fishrock123 added a commit to Fishrock123/node that referenced this pull request Dec 21, 2017
A sort-of follow-up to nodejs#17704, this
removes the last internal use of enroll().
BridgeAR pushed a commit to BridgeAR/node that referenced this pull request Dec 28, 2017
A sort-of follow-up to nodejs#17704, this
removes the last internal use of enroll().

PR-URL: nodejs#17800
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
MylesBorins pushed a commit that referenced this pull request Jan 8, 2018
A sort-of follow-up to #17704, this
removes the last internal use of enroll().

PR-URL: #17800
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
MylesBorins pushed a commit that referenced this pull request Jan 9, 2018
A sort-of follow-up to #17704, this
removes the last internal use of enroll().

PR-URL: #17800
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
@apapirovski
Copy link
Member

@Fishrock123 Did you have a plan to backport parts of this? I noticed #18062 and #18065 depend on some of the changes here, including the fact that there's a new lib/internal/timers.js file.

There are also follow up PRs that landed since that aren't semver-major but depend on some of the changes here.

I would be happy to help, if needed.

@Fishrock123
Copy link
Contributor Author

No, I don’t really have time to do backports...

@apapirovski
Copy link
Member

No worries. I'll try to figure out a way we can get this backported. I assume we should be able to do it if we just exclude the net changes.

apapirovski added a commit to apapirovski/node that referenced this pull request Apr 11, 2018
A bug was introduced in nodejs#17704 which meant that subsequent
calls to enroll would unset the new _idleTimeout and the
enrolled object could never again function as a timer.
apapirovski added a commit that referenced this pull request Apr 17, 2018
A bug was introduced in #17704 which meant that subsequent
calls to enroll would unset the new _idleTimeout and the
enrolled object could never again function as a timer.

PR-URL: #19936
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
jasnell pushed a commit that referenced this pull request Apr 17, 2018
A bug was introduced in #17704 which meant that subsequent
calls to enroll would unset the new _idleTimeout and the
enrolled object could never again function as a timer.

PR-URL: #19936
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@addaleax addaleax mentioned this pull request Oct 20, 2018
2 tasks
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. http2 Issues or PRs related to the http2 subsystem. net Issues and PRs related to the net subsystem. semver-major PRs that contain breaking changes and should be released in the next major version. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants