-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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: fix incorrect headersTimeout measurement #30184
Conversation
@@ -160,6 +161,7 @@ const parsers = new FreeList('parsers', 1000, function parsersCb() { | |||
parser[kOnHeadersComplete] = parserOnHeadersComplete; | |||
parser[kOnBody] = parserOnBody; | |||
parser[kOnMessageComplete] = parserOnMessageComplete; | |||
parser[kOnMessageBegin] = 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.
my understanding was that onMessageBegin will be called when the parser starts parsing a new request's headers.
@@ -520,11 +519,17 @@ function socketOnData(server, socket, parser, state, d) { | |||
onParserExecuteCommon(server, socket, parser, state, ret, d); | |||
} | |||
|
|||
function onParserMessageBegin(parser) { | |||
// We are starting to wait for the headers. | |||
parser.parsingHeadersStart = nowDate(); |
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.
So when the parsing starts we capture when it started.
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 guess it's out of scope for this PR, but why are we depending on Date.now()
for these things instead of the HR timer? What if a big NTP-sync shift happens during intervals like this? It could have a profound impact on how things run.
Local<Value> cb = obj->Get(env()->context(), | ||
kOnMessageBegin).ToLocalChecked(); | ||
|
||
if (!cb->IsFunction()) |
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.
will it affect performance? Not sure if there is a better way. An extra callback will be invoked for every request
@@ -0,0 +1,50 @@ | |||
'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.
this test fails on master with a timeout, and it's based on the test cases mentioned in #27363 This PR attempts to fix the problem.
4018ecf
to
4fc9095
Compare
@@ -14,7 +14,7 @@ let requestReceived = 0; | |||
const server = http.createServer(function(req, res) { | |||
const id = ++requestReceived; | |||
const enoughToDrain = req.connection.writableHighWaterMark; | |||
const body = 'x'.repeat(enoughToDrain * 100); | |||
const body = 'x'.repeat(enoughToDrain * 200); |
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 the single unrelated test that is failing. I saw in its history that it was fixed as a flaky in the past. It seems to fails again and I don't think that it's related to my changes.
There are failing tests. One of them is testing async_hooks. Before the change the list of activities looks like this:
After the change there is an additional tick object:
|
It looks like the usage of async functions affects what async hooks report in other tests. It seems those tests capture arbitrary side effects which are internal implementation of http server. Adding the following line back should fix some of broken tests: dc9fc6a although it's not needed for the logic anymore |
Benchmark CI results
I think the only relevant ones are
but I’m not sure if we need to do anything about those if this fixes an actual bug and doesn’t affect the benchmarks for HTTP altogether. |
Perhaps there is some other way to fix the bug? without adding a new callback? |
The performance should not be worried about too much as long as this fixes a bug. The performance could probably be improved later on. @nodejs/http @nodejs/http2 @nodejs/streams @ronag PTAL |
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, it would be good to have a backport PR for 12.x and 10.x as there is still the old http_parser there.
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 although I'd prefer reviews from more http
experts. @nodejs/http
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
test.parallel/test-http-highwatermark is failing consistently on Windows with this change. |
18:35:43 not ok 238 parallel/test-http-highwatermark
18:35:43 ---
18:35:43 duration_ms: 0.244
18:35:43 severity: fail
18:35:43 exitcode: 1
18:35:43 stack: |-
18:35:43 Mismatched <anonymous> function calls. Expected exactly 1, actual 0.
18:35:43 at Object.mustCall (C:\workspace\node-test-binary-windows-js-suites\node\test\common\index.js:325:10)
18:35:43 at Server.<anonymous> (C:\workspace\node-test-binary-windows-js-suites\node\test\parallel\test-http-highwatermark.js:21:41)
18:35:43 at Server.emit (events.js:321:20)
18:35:43 at parserOnIncoming (_http_server.js:782:12)
18:35:43 at HTTPParser.parserOnHeadersComplete (_http_common.js:117:17)
18:35:43 Mismatched <anonymous> function calls. Expected exactly 1, actual 0.
18:35:43 at Object.mustCall (C:\workspace\node-test-binary-windows-js-suites\node\test\common\index.js:325:10)
18:35:43 at Server.<anonymous> (C:\workspace\node-test-binary-windows-js-suites\node\test\parallel\test-http-highwatermark.js:28:43)
18:35:43 at Server.emit (events.js:321:20)
18:35:43 at parserOnIncoming (_http_server.js:782:12)
18:35:43 at HTTPParser.parserOnHeadersComplete (_http_common.js:117:17)
18:35:43 ... |
@Trott It looks like a flaky test for me. It's been fixed in the past as being flaky and I had to change it because it was failing for me. I will remove that change to see if it passes after that. |
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 avoid calling from C++ to JavaScript? This will affect performance.
I would prefer if we did not regress on the performance of our http implementation quite a bit, and it's one of our core metrics.
@mcollina I have no alternative implementation for this issue yet. If someone has any ideas, please share. |
I think we might try keeping the current time in C++ instead and implementing that logic there instead. If we decide we want to land this anyway as it is, let's open a new issue to track the perf regression. |
cc @nodejs/tsc |
For keep-alive connections, the headersTimeout may fire during subsequent request because the measurement was reset after a request and not before a request. Fixes: nodejs#27363
b51887b
to
01c2228
Compare
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 time tracking absolutely should remain at the c++ level. It's not cost free but uv_hrtime()
should be used. If the timestamps need to be exposed to JavaScript then an aliased array can be used.
I want to point out that time-tracking was in JS already. This PR only adds onMessageBegin callback to the parser to be able to reset the var at the right time. I am looking into moving time-tracking code into C++ atm. |
@mcollina I got a partially working implementation where the time is being tracked in node_http_parser.cc using uv_hrtime. Is node_http_parser.cc an appropriate place for that? I don't find any other timers in the parser code at the moment. Currently, I check the time in OnStreamRead before kOnExecute is invoked. What would be the best way to trigger a timeout error from here? Should I add a new callback (e.g., |
One possible solution to take the sting out:
That makes the timestamp collection basically free from the POV of the main thread because the cost is paid by the watchdog thread. We already have a watchdog thread that can be repurposed/extended for this. I can put up a prototype if people feel the above is a good way forward. |
I have opened a PR with an alternative implementation where the time spent parsing headers is tracked by the parser: #32329 |
Sounds like anything that will meet approval from others will be a fair bit different. I can re-review if this changes.
Closed in favor of #32329 |
For keep-alive connections, the headersTimeout may fire during subsequent requests
because the measurement was reset after a request and not before a request.
Fixes: #27363
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes