-
Notifications
You must be signed in to change notification settings - Fork 30k
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: improve outgoing string write performance #13013
Conversation
24d3a7e
to
171ad41
Compare
Thoughts @nodejs/collaborators ? |
I would mark this semver-major. I can imagine code out there on the receiving-end may depend on packets containing that first chunk. Not a good thing, but probably best to assume that exists. |
@@ -78,6 +78,9 @@ utcDate._onTimeout = function _onTimeout() { | |||
}; | |||
|
|||
|
|||
function noopPendingOutput(amount) {} |
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.
Mmmm, I thought our linter would complain about amount
. It didn't.
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 AsyncHooks changes doesn't appear to have anything to do with this optimization, is there another PR or can you comment on why these changes were made?
edit: I see #13045 now. I'm not really comfortable with the socket._handle.asyncReset
change until we understand why.
lib/_http_agent.js
Outdated
@@ -167,7 +167,8 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/, | |||
// we have a free socket, so use that. | |||
var socket = this.freeSockets[name].shift(); | |||
// Assign the handle a new asyncId and run any init() hooks. | |||
socket._handle.asyncReset(); | |||
if (socket._handle.asyncReset) |
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.
Why is this necessary?
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.
Might not be necessary. At Least for the TLS case.
Ref #13092
lib/_http_agent.js
Outdated
@@ -182,7 +183,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/*legacy*/, | |||
// If we are under maxSockets create a new one. | |||
this.createSocket(req, options, function(err, newSocket) { | |||
if (err) { | |||
nextTick(newSocket._handle.getAsyncId(), function() { | |||
process.nextTick(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.
Is this because the newSocket._handle
isn't set when an error occurs?
@AndreasMadsen Pay no attention to the async hooks workaround commit as it's temporary. I added it so that citgm and the like will work for this PR. |
@@ -117,7 +120,7 @@ function OutgoingMessage() { | |||
this._header = null; | |||
this[outHeadersKey] = null; | |||
|
|||
this._onPendingData = null; | |||
this._onPendingData = noopPendingOutput; |
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 you don't need it's name to unregister why not just () => {}
?
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.
To avoid creating a new noop for each response.
this.outputEncodings.unshift('latin1'); | ||
this.outputCallbacks.unshift(null); | ||
this.outputSize += this._header.length; | ||
if (typeof this._onPendingData === '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.
I like the "polymorphic" approach 👍
Question is the noop call faster than the if (typeof
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 didn't measure it by itself, but it should be to some degree since it no longer has to execute a conditional before calling the function.
return false; | ||
}; | ||
} |
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.
Isn't this the end of OutgoingMessage.prototype._send = function _send
, so should have ;
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.
No, this is the end of _writeRaw()
which uses a function declaration, so there is no semicolon needed.
@@ -37,6 +37,7 @@ server.listen(0, common.mustCall(function() { | |||
headers: { connection: 'keep-alive' } | |||
}, common.mustCall(function(res) { | |||
server.close(); | |||
serverRes.destroy(); |
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.
What is the benefit of moving it here?
Found the comment.
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.
Some comments and quastions
I'm +1, since except for the performance, IMHO it makes the code more readable.
and more consistent. |
171ad41
to
fcde51c
Compare
fcde51c
to
3251353
Compare
Thoughts on semver-ness @nodejs/ctc ? |
hmm... really hard to say. Apps really shouldn't be depending on the data coming with the header. I think I'm ok with this as a patch. |
Given some recent digging I did, I would be cautious. I would say semver-patch, but don't backport to LTS for several months (if at all). |
var header = this._header; | ||
if (this.output.length === 0) { | ||
this.output = [header]; | ||
this.outputEncodings = ['latin1']; |
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.
General note on how we handle the response body. Browsers treat 'charset=latin1'
as windows-1252
, which is problematic. Example:
const headers =
'HTTP/1.1 200 OK\r\n' +
'Content-Type: text/html; charset=latin1\r\n' +
'Content-Length: 2\r\n\r\n' +
// Technically 0x83 is a control code, but browsers don't see it that way.
'\u0083\n';
const data = Buffer.from(headers, 'latin1');
require('net').createServer(c => c.end(data)).listen(8778);
Hit that with the browser and you'll see ƒ
, not a control character symbol.
semver-patch with no backporting works for me. |
@jasnell Isn't that effectively semver-major then, or were you thinking about landing it in v8.0.0? |
Any reviews/approvals for this PR @nodejs/collaborators ? |
@mscdex ... not necessarily because after we give it a few months we may decide that it's safe to go ahead and pull back. |
3251353
to
eb7c38d
Compare
CI before landing: https://ci.nodejs.org/job/node-test-pull-request/8317/ |
PR-URL: nodejs#13013 Reviewed-By: James M Snell <jasnell@gmail.com>
eb7c38d
to
a10bdb5
Compare
PR-URL: #13013 Reviewed-By: James M Snell <jasnell@gmail.com>
These changes could be semver-major-y. One behavioral change introduced by this commit is that when writing a string that is the first chunk, it is no longer guaranteed to be sent immediately with the header. This is why the two tests had to be modified. It should be noted that this behavioral change actually brings it in line with what happens when writing a first
Buffer
chunk, so there would be consistency after this change. However I'm not sure if there is anyone that may be relying on the old string writing behavior...One other possibly noticeable change is that
chunk
/data
inend()
is no longer validated if the outgoing message is already finished.Anyway, here are some results:
CI: https://ci.nodejs.org/job/node-test-pull-request/8060/
CITGM: https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/789/
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)