-
Notifications
You must be signed in to change notification settings - Fork 4.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
Send RST_STREAM instead EOF when sending request body is cancelled #58548
Send RST_STREAM instead EOF when sending request body is cancelled #58548
Conversation
Tagging subscribers to this area: @dotnet/ncl Issue DetailsFixes #58498
|
I need to say that I still have a concern regarding how Kestrel will react to such RST_STREAM with CANCEL. We expect that it will continue sending the response body (if any), but we need to verify it. |
@geoffkizer I discussed this behavior with the Kestrel team and we decided that when the request body is skipped the RST_STREAM with CANCEL should be sent after the response body receival has completed. This is required by the HTTP/2 stream states transition rules as described RFC 7540 section 5.1. cc: @Tratcher
|
/azp run runtime-libraries stress-http |
Azure Pipelines successfully started running 1 pipeline(s). |
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.
Looks good, just one question. Thanks!
if (frame == null && expectEndOfStream) | ||
|
||
// Check if it was a zero-byte read or we got a RST_STREAM with the CANCEL code. | ||
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) |
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 a suggestion. I had to lookup operator precedence of &&
and ||
to make sure I understand the condition correctly.
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) | |
if ((frame == null || (frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8)) && expectEndOfStream) |
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 see the confusion, but then we need to add even more parenthesis because there are more ||
and &&
operations. Don't you think it could make the code less readable?
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) | |
if ((frame == null || ((frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8)) && expectEndOfStream)) |
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.
Now I'm fully confused what the right order of evaluation is...
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.
&&
takes precedence over ||
so the order is
- frame == null
OR - frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8 && expectEndOfStream
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 about this?
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) | |
if ((frame == null | |
|| (frame.Type == FrameType.RstStream | |
&& ((RstStreamFrame)frame).ErrorCode == 0x8)) | |
&& expectEndOfStream) |
Or for simplicity, move expectedEndOfStream
to the beginning:
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) | |
if (expectEndOfStream | |
&& (frame == null | |
|| (frame.Type == FrameType.RstStream | |
&& ((RstStreamFrame)frame).ErrorCode == 0x8))) |
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 we need to change the code to make order of || and && explicit. It is not something anyone wants to keep looking up and I bet that 99% of developers don't remember it. I will rather survive more parenthesis than mind-blowing thinking about || and && precedence.
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 way then?
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) | |
if (expectEndOfStream | |
&& (frame == null || (frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8))) |
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.
IMO at minimum this:
if ((frame == null || frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8) && expectEndOfStream) | |
if (expectEndOfStream | |
&& (frame == null || (frame.Type == FrameType.RstStream && ((RstStreamFrame)frame).ErrorCode == 0x8))) |
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
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.
Haha, that's what I suggested initially (with the exception of moving expectEndOfStream
at the beginning) 😄
@@ -1307,6 +1316,13 @@ private void CloseResponseBody() | |||
{ | |||
Cancel(); | |||
} | |||
else if (_sendRstOnResponseClose) |
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 called with response Dispose
. Which works great for cancelling partially consumed responses. But for fully consumed response - if the user doesn't Dispose the response - we would be waiting for GC to collect the stream and send the RST. Shouldn't we rather send it immediately after we reach the end of response stream?
Note that I'm not saying this is wrong, just thinking of possible scenarios that could happen.
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.
Hmm. You are right, delaying sending seems as an issue. Do you know how and where we can react to reaching the end of response stream?
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'd say in this else block:
runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs
Lines 1114 to 1118 in 49ea27e
else | |
{ | |
// We've hit EOF. Pull in from the Http2Stream any trailers that were temporarily stored there. | |
MoveTrailersToResponseMessage(responseMessage); | |
} |
Sounds right to me. |
Fixes #58498