-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
http2: use and support non-empty DATA frame with END_STREAM flag #33875
Conversation
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 sounds good, but I’m a bit surprised that we didn’t catch this already … is there a way to add a test for this?
@addaleax Let me give it another shot now that I understand the original bug. It may not be possible because of the http2 client is based on Also, let me know if commits needs to be squashed. |
@addaleax Doesn't seem possible to test with the internal http2 client. On a side-note, we should probably fix that eventually. No reason to send in two frames what can be sent in one. |
WAIT!
https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback This isn't a valid fix. We need to tackle it differently. I have no idea why it says that, but I think we should follow it. Do they mean out of order frames? I'll want a little more time to diagnose it better. Line 1751 in f46ca0f
is my next guess for a better solution. |
@clshortfuse I don’t really know either… also, we do have a check for |
@addaleax The usage there is right and we're doing it right. Line 1277 in f46ca0f
The only thing I can think of that the Taking a look back, the real problem is the pause. We can do a hack and just not pause on the last It's also entirely possible this could be a |
So here's what's going on: When we call Line 1098 in f46ca0f
We are telling And while we check to see if we paused: Lines 752 to 763 in f46ca0f
We only return here if and only if what we read ( Lines 765 to 768 in f46ca0f
We pause but never resume. When we do resume, because a NEW frame comes in, then in that sequence we get both the
So despite the comment saying we are done with the chunk, we actually aren't. There's still the Lines 1457 to 1459 in f46ca0f
So one possible fix is to always return in Or, check |
src/node_http2.cc
Outdated
@@ -755,7 +755,7 @@ ssize_t Http2Session::ConsumeHTTP2Data() { | |||
CHECK_GT(ret, 0); | |||
CHECK_LE(static_cast<size_t>(ret), read_len); | |||
|
|||
if (static_cast<size_t>(ret) < read_len) { | |||
if (static_cast<size_t>(ret) <= read_len) { |
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.
Hm – the CHECK_LE
above this verifies that this is always the case, right?
(Also, thanks for digging into this so deeply!)
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 spotted that too, but I wasn't sure. It's very possible this all comes from a single missing character!
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 reality, the whole conditional of if (static_cast<size_t>(ret) < read_len) {
is suspect. I don't think we need it, but I leaned on <=
instead because it replicates the CHECK_LE
line.
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.
Hm … trying to think this through: If reading is paused but we still consumed the entire buffer, doesn’t that mean that we should clear the Buffer in the code below (i.e. stream_buf_offset_ = 0;
etc)?
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.
We don't need the buffer, no. But we can't reset stream_buf_offset_
because we need to recall ConsumeHTTP2Data()
later:
I was thinking (stream_buf_offset_ > 0 || is_receive_paused())
, but then it'll try to run ConsumeHTTP2Data()
every time you write something, even if the readable side is completed.
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.
@clshortfuse So, just to make sure I understand this correctly: We should also call nghttp2_session_mem_recv()
even if no new data is available from the socket, and the previous nghttp2_session_mem_recv()
call reported all data as read, if we entered pause state during that previous call?
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.
Maybe we just shouldn’t pause in the first place if NGHTTP2_FLAG_END_STREAM
is set?
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 Yes. I tried to keep the comment to the point, since I'm known for blabbing 🙃, but the nghttp2
documentation states:
If the application uses nghttp2_session_mem_recv(), it can return NGHTTP2_ERR_PAUSE to make nghttp2_session_mem_recv() return without processing further input bytes. The memory by pointed by the data is retained until nghttp2_session_mem_recv() or nghttp2_session_recv() is called. The application must retain the input bytes which was used to produce the data parameter, because it may refer to the memory region included in the input bytes.
https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback
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.
Maybe we just shouldn’t pause in the first place if NGHTTP2_FLAG_END_STREAM is set?
Sorry, I missed this. That is entirely possible, but it would be less efficient memory-wise. The sooner we pause to send out an output chunk, the sooner it's released from RAM. Also, we have no way of knowing how many times the current data frame (though it has an END_STREAM
flag) will trigger a read callback. Frames can be upto 16MiB in size. So, hypothetically, if nghttp2
is sending us things in 1KB chunks, it could take 16,384 iterations before we send out whatever data we want. That could be any outbound frame type, including PING
responses.
First, thanks to everyone who is working to make all this stuff great for workaday Nodejs users like me. It looks like all that we're waiting on is the cpp linter issue. Is there any guess as to how long it might take to see this in a release? If I could help, I would! Thanks again. |
@jeffmcmahan Thanks for the encouragement. While it may look like it's just linters that's missing, that's just the CI (continuous integration). There's still some actual testing being needed. Some of this may be have to be manually tested due to this issue: #33891 I can't speak to how NodeJS determines their release schedule, but, personally, I'm targeting resolving things this week. I'll be posting a comment with a workaround in the related issue, so you're not waiting on this exact PR to be merged or the eventually release. |
@clshortfuse - Thanks so much. |
I can confirm this fixes the issue with Chrome,
We actually just removed an extra Still working on devising a test, if at all possible. |
I can't build a test because we're blocked by #33891 I'll leave the in-depth details there, but essentially, this the associated bug only occurs on a final DATA frame with a payload. Our client never sends a final DATA frame with a payload. It will only send a blank one. Once that is resolved, then we can build a test for this. Though... once the client does start sending non-blank final DATA frames, then it'll have a compatibility issue with NodeJS HTTP2 servers that don't have this PR's fix. |
I couldn't make both fixes into separate PRs. The client and server portions have to happen in the same commit. Now I can write some new tests. |
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. When writing to a stream, uses a END_STREAM flag on final DATA frame instead of adding an empty DATA frame. BREAKING: http2 client now expects servers to properly support END_STREAM flag Fixes: nodejs#31309 Fixes: nodejs#33891 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback
I'm a bit new to backporting, but I'll take a shot. I should learn it at some point. So, this PR is for master, and we need a new PR for |
It would be a PR against the v14.x-staging branch If you branch off v14.x-staging you can land my backport via curl -L https://github.com/MylesBorins/node/commit/5ab93d517c54bb3e8fe8951eb5d2abb7834a4654.patch | git am Alternatively you can |
v14.x and v12.x backport PRs are in. v10.x backport might need to be degraded to just "support non-empty DATA frame with END_STREAM flag" instead of "use and support". It's a bit more complicated to make it pack the last frame on send because of how old the codebase is. In reality, v10.x is in maintenance anyway. Supporting non-empty DATA frames is a bugfix. Using them is added functionality which goes outside the scope of LTS maintenance. Edit: v10.x PR is in. |
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. Fixes: nodejs#31309 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback PR-URL: nodejs#33875 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. When writing to a stream, uses a END_STREAM flag on final DATA frame instead of adding an empty DATA frame. BREAKING: http2 client now expects servers to properly support END_STREAM flag Fixes: #31309 Fixes: #33891 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback PR-URL: #33875 Backport-PR-URL: #34838 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
This is also a related HTTP/2 bug, that would be great to have backported to v12 #30894. |
It looks like this was released in v14.9.0 already per https://nodejs.org/en/blog/release/v14.9.0/. Any updates on the backports being merged and released before September 15 security updates @mhdawson ? cc @MylesBorins @addaleax |
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. Fixes: #31309 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback PR-URL: #33875 Backport-PR-URL: #34857 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. When writing to a stream, uses a END_STREAM flag on final DATA frame instead of adding an empty DATA frame. BREAKING: http2 client now expects servers to properly support END_STREAM flag Fixes: nodejs#31309 Fixes: nodejs#33891 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback PR-URL: nodejs#33875 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. When writing to a stream, uses a END_STREAM flag on final DATA frame instead of adding an empty DATA frame. BREAKING: http2 client now expects servers to properly support END_STREAM flag Fixes: #31309 Fixes: #33891 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback Backport-PR-URL: #34845 PR-URL: #33875 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
Adds support for reading from a stream where the final frame is a non-empty DATA frame with the END_STREAM flag set, instead of hanging waiting for another frame. When writing to a stream, uses a END_STREAM flag on final DATA frame instead of adding an empty DATA frame. BREAKING: http2 client now expects servers to properly support END_STREAM flag Fixes: #31309 Fixes: #33891 Refs: https://nghttp2.org/documentation/types.html#c.nghttp2_on_data_chunk_recv_callback Backport-PR-URL: #34845 PR-URL: #33875 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
fixes #31309
fixes #33891
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes