-
Notifications
You must be signed in to change notification settings - Fork 30.2k
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
Merge http2 stream & net socket #19527
Conversation
I think we should be avoiding adding new methods/properties, underscore-prefixed or not. At the very least we should be using Symbols to hide such implementation details. Changes like this will also need to be benchmarked appropriately. |
lib/internal/streams/stream_base.js
Outdated
const StreamBaseMethods = { | ||
createWriteWrap(req, streamId, handle, oncomplete) { | ||
if (streamId) | ||
req.stream = streamId; |
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.
I think this could remain in the HTTP/2 code, it’s specific to the protocol.
lib/internal/streams/stream_base.js
Outdated
@@ -0,0 +1,80 @@ | |||
'use strict'; |
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.
Could you move this file into lib/internal/
? The streams/
directory is currently used almost exclusively for the stream
module itself, but this implements parts of network & HTTP/2 streams
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.
aha i see, makes sense. doing it right now.
lib/net.js
Outdated
const handle = this._handle; | ||
this.createWriteWrap(req, null, handle, afterWrite); | ||
if (writev) ret = this.writevGeneric(req, data, cb); | ||
else ret = this.writeGeneric(req, data, encoding, cb); |
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.
Style nit: Can you put the bodies on their own lines? That’s more common style inside Node core, I think
@mscdex updated code to use symbols, can you please take a look to see if it looks good? |
lib/internal/stream_base.js
Outdated
} | ||
|
||
const StreamBase = { | ||
[kCreateWriteWrap](req, handle, oncomplete) { |
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.
I would call this something else, since it's not actually creating and returning a WriteWrap
instance. Perhaps something like kSetupWriteWrap
or similar?
lib/internal/stream_base.js
Outdated
if (err) { | ||
return this.destroy(errnoException(err, 'write', error), cb); | ||
} | ||
return null; |
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.
This is unnecessary and can be removed.
lib/internal/stream_base.js
Outdated
if (err) { | ||
return this.destroy(errnoException(err, 'write', error), cb); | ||
} | ||
return null; |
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.
This is unnecessary and can be removed.
lib/net.js
Outdated
@@ -307,6 +313,7 @@ function Socket(options) { | |||
this[BYTES_READ] = 0; | |||
} | |||
util.inherits(Socket, stream.Duplex); | |||
StreamBase.apply(Socket.prototype); |
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.
Can we get rid of apply
and use Object.assign()
? I find it cleaner.
Object.assign(Socket.prototype, StreamBase);
I also find the StreamBase
name a bit odd for a set of mixins.
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.
yea, i agree. i called it StreamBaseMethods
prior to this... maybe StreamSharedMethods
? and +1 on using Object.assign
will do soon
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.
I would still prefer prefer if the methods were top-level functions rather than properties of the prototype. However I wouldn't put a -1 on that one.
I would also rename the file to lib/internal/stream_base_commons.js
. Those are tightly couple to StreamBase, which is slightly different from require('stream')
.
👍 to @mcollina's suggestion of using top-level functions. That would also allow us to not use symbols. |
I also prefer top-level functions, especially since some were already that way to begin with. My suggestion of Symbols was if we absolutely needed them as properties. |
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.
A good start but this needs some cleanup. I wholeheartedly agree with the suggestions to use top level functions.
} | ||
|
||
const StreamSharedMethods = { | ||
[kCreateWriteWrap](req, handle, oncomplete) { |
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.
This might as well create the WriteWrap and return it.
}, | ||
|
||
[kWriteGeneric](req, data, encoding, cb) { | ||
const { handle, error } = req; |
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.
The error
can't be gotten out of ahead of time like this, AFAIK. Also, destructuring assignment with more than 1 variable is still pretty slow last time I tested it.
[kWriteGeneric](req, data, encoding, cb) { | ||
const { handle, error } = req; | ||
|
||
const err = _handleWriteReq(req, handle, data, encoding); |
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.
Don't get the handle out of req
just to pass it in to the function which already accepts req
. Just do that work in _handleWriteReq
which IMO should drop the leading _
. I don't see any reason for it.
req.async = false; | ||
}, | ||
|
||
[kWritevGeneric](req, data, cb) { |
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.
Just make all of these helper functions. There's no need for them to exist on the prototype. this
can be a param of the function.
const err = _handleWriteReq(req, handle, data, encoding); | ||
|
||
if (err) | ||
return this.destroy(errnoException(err, 'write', error), cb); |
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.
In relation to a comment further up above, this should be req.error
.
const kWriteGeneric = Symbol('writeGeneric'); | ||
|
||
function _handleWriteReq(req, handle, data, encoding) { | ||
switch (encoding) { |
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.
Just get the handle from the req here instead of passing it in.
lib/net.js
Outdated
err = this._handle.writev(req, chunks, allBuffers); | ||
let ret; | ||
const req = new WriteWrap(); | ||
const handle = this._handle; |
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.
This assignment is unnecessary. Just pass in this._handle
below.
lib/internal/http2/core.js
Outdated
@@ -1675,13 +1657,10 @@ class Http2Stream extends Duplex { | |||
const handle = this[kHandle]; |
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.
This assignment is now unnecessary. Just pass in this[kHandle]
down below.
lib/internal/http2/core.js
Outdated
req.async = false; | ||
const err = createWriteReq(req, handle, data, encoding); | ||
if (err) | ||
throw errnoException(err, 'write', req.error); |
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.
The semantics here are now different. We're calling destroy with an error instead of just throwing. IMO we should just restore the if (err)
blocks and not have that code inside the helpers.
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.
Actually, I see now that @addaleax requested this. Since this would only destroy the stream, I suppose that should be fine.
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.
@apapirovski Fwiw, this is taken from #19389, so that one should land first
lib/net.js
Outdated
|
||
if (err) | ||
return this.destroy(errnoException(err, 'write', req.error), cb); | ||
if (ret) return ret; |
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.
Given the note above re: changed semantics for http2, I would rather we just test for err
and then call this.destroy
here rather than inside the helpers. The if (ret) return ret;
is super opaque as to what the return value is or its meaning.
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.
Since @addaleax requested this related change, I rescind the feedback above. That said, a comment should be left indicating that if (ret) return ret;
means there was an error and this.destroy
was called with it.
Since this.destroy
returns this
, the same thing could be communicated without a comment. The variable could be changed to be err
and then the code would be if (err) return this;
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.
@apapirovski Just for context, I talked with @aks- about this and I don’t think it’s a good permanent solution anyway, but doing more might require some changes to the bytesWritten
tracking for net
But yes, a comment here would be very helpful
16f9ee6
to
560635d
Compare
i've updated the code. i think it looks good now? included ~all the suggestions |
if (err) | ||
return this.destroy(errnoException(err, 'write', req.error), cb); | ||
|
||
writeGeneric(this, req, data, encoding, cb); |
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.
Would be nice to have this done for writev
too :)
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.
@addaleax done 👍
lib/net.js
Outdated
|
||
if (err) | ||
return this.destroy(errnoException(err, 'write', req.error), cb); | ||
if (ret) return ret; |
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.
I think there was a suggestion to leave a comment somewhere saying that this line is only run in case of an error?
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.
done :D
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
lib/net.js
Outdated
|
||
if (err) | ||
return this.destroy(errnoException(err, 'write', req.error), cb); | ||
// this only runs when writeGeneric returns error. |
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.
Nit: the comment is a bit misleading in a couple of ways:
- It can also run when
writevGeneric
fails. - Both
writeGeneric
andwriteGeneric
returnself
when an error occurs instead of the actual error.
I'd use something like "Bail out if handle.write*
returned an error".
// Retain chunks | ||
if (err === 0) req._chunks = chunks; | ||
|
||
if (err) |
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.
Nit: it should be ok to use an else
branch on the previous if
statement for this.
lib/internal/stream_base_commons.js
Outdated
function writevGeneric(self, req, data, cb) { | ||
const allBuffers = data.allBuffers; | ||
let chunks; | ||
let i; |
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.
Can you restore these as var
? I'm not sure since when let
variables are on par with var
variables for tight loop performance, but this is one of the hottest path in core, I would prefer if we used very fast ES5
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.
cc @bmeurer
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.
@mcollina I might be wrong, but I think it shouldn’t make a difference for variables that are at the top level of a function?
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.
const or let have some overhead in Node 8 and 9 in tight loops such as this one. I’m not sure if this still the case with current master.
I’d prefer if this refactoring was separated from changing from var to const/let.
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.
@mcollina makes sense, i'll change them back to var
lib/internal/stream_base_commons.js
Outdated
} else { | ||
chunks = new Array(data.length << 1); | ||
for (i = 0; i < data.length; i++) { | ||
const entry = data[i]; |
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.
can you make this a var as it was?
lib/net.js
Outdated
err = createWriteReq(req, this._handle, data, encoding); | ||
} | ||
let ret; | ||
const req = createWriteWrap(this._handle, afterWrite); |
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.
can you make these vars?
For the current version (without @mcollina’s nits): CI: https://ci.nodejs.org/job/node-test-commit/17168/ |
@mcollina changes made to use back |
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
Landed in c2835e5 🎉 |
Squashed from: - lib: separate writev responsibilities from writeGeneric - lib: fix calling of cb twice - lib: extract streamId out of stream_base to caller - lib: add symbols instead of methods to hide impl details - lib: remove unneeded lines - lib: use Object.assign instead of apply - lib: rename mixin StreamBase to StreamSharedMethods - lib: use stream shared funcs as top level instead of properties of prototypes - lib: mv lib/internal/stream_shared_methods.js lib/internal/stream_base_commons.js - lib: add comment for readability - lib: refactor _writev in Http2Stream - lib: rephrase comment - lib: revert usage of const,let for perf reasons PR-URL: #19527 Refs: #19060 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
Should this be backported to |
Squashed from: - lib: separate writev responsibilities from writeGeneric - lib: fix calling of cb twice - lib: extract streamId out of stream_base to caller - lib: add symbols instead of methods to hide impl details - lib: remove unneeded lines - lib: use Object.assign instead of apply - lib: rename mixin StreamBase to StreamSharedMethods - lib: use stream shared funcs as top level instead of properties of prototypes - lib: mv lib/internal/stream_shared_methods.js lib/internal/stream_base_commons.js - lib: add comment for readability - lib: refactor _writev in Http2Stream - lib: rephrase comment - lib: revert usage of const,let for perf reasons PR-URL: nodejs#19527 Refs: nodejs#19060 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
extract out codebase which is identical in both net.js and http2/core.js into new
stream_base
object.this should conclude #19060
i'm working on another pr where we can standardize the property name between net.js and http2/core.js. and refactor it further.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes