-
Notifications
You must be signed in to change notification settings - Fork 5.3k
fix MaxResponseHeadersLength tests #74479
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
Conversation
|
Tagging subscribers to this area: @dotnet/ncl Issue DetailsThis is mostly test problem. I saw occasional failures also on HTTP/1.1 now, there are some significant differences and reasons why HTTP/3 fails often: runtime/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs Lines 165 to 169 in 4734ee0
and then on receiving side something like (frameType, payloadLength) = await ReadFrameEnvelopeAsync(cancellationToken).ConfigureAwait(false);
private async ValueTask ReadHeadersAsync(long headersLength, CancellationToken cancellationToken)
{
// TODO: this header budget is sent as SETTINGS_MAX_HEADER_LIST_SIZE, so it should not use frame payload but rather 32 bytes + uncompressed size per entry.
// https://tools.ietf.org/html/draft-ietf-quic-http-24#section-4.1.1
if (headersLength > _headerBudgetRemaining)
{
_stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.ExcessiveLoad);
throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection.Pool.Settings.MaxResponseHeadersByteLength));
}we get header length from the frame header and we can fail and Abort without even waiting for the headers (and sending task to complete) The fix for this is to simply swallow transport errors. Now looking at it, it seems like we are aborting wrong side. It somewhat does not matter as we will fail and abort both sides immediately but that delivers wrong application code to the peer. Lastly I was trying to use tracing to figure out what is happening I finally succeeded but I had to improve it somewhat. so it is clear we received 3 bytes out of promised 15400 and aborted immediately. Peer (e.g. LoopBack server) will get PEER_RECEIVE_ABORTED before completing write. fixes #73930
|
rzikm
left a 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.
LGTM, small nits
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs
Outdated
Show resolved
Hide resolved
MihaZupan
left a 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.
Thanks for catching the H3 issue
| if (headersLength > _headerBudgetRemaining) | ||
| { | ||
| _stream.Abort(QuicAbortDirection.Write, (long)Http3ErrorCode.ExcessiveLoad); | ||
| _stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.ExcessiveLoad); |
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.
With the previous behavior of closing the write direction, were things still eventually being cleaned up correctly?
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.
Yes, you can sort of see it ion the posted logs. Frirst comes from the ReadHeadersAsync above.
01:40:10.3532604[Info] thisOrContextObject: QuicStream#41727345, memberName: Abort, message: [strm][0x7FA554013E40] Aborting Read with 263.
01:40:10.3546351[Info] thisOrContextObject: QuicStream#41727345, memberName: Abort, message: [strm][0x7FA554013E40] Aborting Write with 258.
01:40:10.3547886[Info] thisOrContextObject: QuicStream#41727345, memberName: Abort, message: [strm][0x7FA554013E40] Aborting Read with 268.
I think this comes from HTTP3 error processing. This is easy to reproduce so I can look where this duplicate reset is coming from.
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 it be possible to add ToStrings to the event data structures instead of adding logging into each individual event handler?
Also this change should be done in QuicConnection and QuicListener as well, not just in QuicStream. Or we end up with inconsistent messy logging as we had before.
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
Outdated
Show resolved
Hide resolved
|
|
||
| private unsafe int HandleEventStartComplete(ref START_COMPLETE data) | ||
| { | ||
| if (NetEventSource.Log.IsEnabled()) |
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 also cover QuicConnection and QuicListener for a consistency.
Also could you log the varying arguments in a similar manner, like:
NetEventSource.Info(this, $"{this} Received event START_COMPLETE with {nameof(Status)}={data.Status}, {nameof(PeerAccepted)}={data.PeerAccepted}");
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.
QuicListener did not have anything interesting IMHO so I left is as is. I updated QuicConnection with same pattern.
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.
At least it has a new connection. And this is also about consistency.
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
Outdated
Show resolved
Hide resolved
| data.TotalBufferLength = totalCopied; | ||
| return (_receiveBuffers.HasCapacity() && Interlocked.CompareExchange(ref _receivedNeedsEnable, 0, 1) == 1) ? QUIC_STATUS_CONTINUE : QUIC_STATUS_SUCCESS; | ||
| } | ||
|
|
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.
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? methods should be separated by line IMHO. So as empty line should follow closing brace.
I think we should do separate style pass on the file. @rzikm seems to have best editor setting....
| QUIC_STREAM_EVENT_TYPE.SHUTDOWN_COMPLETE => HandleEventShutdownComplete(ref streamEvent.SHUTDOWN_COMPLETE), | ||
| QUIC_STREAM_EVENT_TYPE.PEER_ACCEPTED => HandleEventPeerAccepted(), | ||
| _ => QUIC_STATUS_SUCCESS | ||
| _ => HandleOtherEvent(streamEvent.Type) |
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.
QuicConnection and QuicListener.
|
|
||
| try | ||
| { | ||
| // Process the event. |
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.
Honestly, why don't we add ToString to the event data structure and log it from one place?
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.
That change would need to go to the generated interop stubs from MsQuic then, right?
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.
Not necessarily, all the structs are partial, so we can add it to a different file. Or just define it as extension/static method.
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 don't think that would be beneficial. It may contains many field (we don't even use) and dumping everything is going to make logs hard to read IMHO. It will incur performance penalty and bring no value.
If we feel strongly about this we can do it at at the Verbose level - we use it in Sockets for example to dump part of the actual data.
I think using nameof() on interesting fields and consistent formatting may be the best
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.
It would be us coding and thus controlling what goes in.
ManickaP
left a 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.
LGTM modulo comments.
I'm still not 100% convinced we should put the logging into every method instead of having ToString or some common way to do it, but I'll not hold this PR on it.
| if (NetEventSource.Log.IsEnabled()) | ||
| { | ||
| NetEventSource.Info(this, $"{this} Connection connected {LocalEndPoint} -> {RemoteEndPoint}"); | ||
| NetEventSource.Error(this, $"{this} Received event CONNECTED {LocalEndPoint} -> {RemoteEndPoint}"); |
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 not an Error
| { | ||
| if (NetEventSource.Log.IsEnabled()) | ||
| { | ||
| NetEventSource.Info(this, $"{this} Received event {type}"); |
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.
| NetEventSource.Info(this, $"{this} Received event {type}"); | |
| NetEventSource.Info(this, $"{this} Received event {type}"); |
|
|
||
| private unsafe int HandleEventStartComplete(ref START_COMPLETE data) | ||
| { | ||
| if (NetEventSource.Log.IsEnabled()) |
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.
At least it has a new connection. And this is also about consistency.
| _connectedTcs.TrySetResult(); | ||
| return QUIC_STATUS_SUCCESS; | ||
| } | ||
|
|
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.
If you insist on putting empty line in between every HandleXYZ then it should be done everywhere. I was intentional with this, I don't mind the change but it should be the same everywhere.
| { | ||
| NetEventSource.Info(this, $"{this} Received event SHUTDOWN_INITIATED_BY_PEER_DATA with {nameof(data.ErrorCode)}={data.ErrorCode}"); | ||
| } | ||
|
|
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.
| { | ||
| if (NetEventSource.Log.IsEnabled()) | ||
| { | ||
| NetEventSource.Info(this, $"{this} Received event {type}"); |
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.
| NetEventSource.Info(this, $"{this} Received event {type}"); | |
| NetEventSource.Info(this, $"{this} Received event {type}"); |
This is mostly test problem. I saw occasional failures also on HTTP/1.1
now, there are some significant differences and reasons why HTTP/3 fails often:
runtime/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs
Lines 165 to 169 in 4734ee0
and then on receiving side something like
we get header length from the frame header and we can fail and Abort without even waiting for the headers (and sending task to complete)
The fix for this is to simply swallow transport errors.
Now looking at it, it seems like we are aborting wrong side. It somewhat does not matter as we will fail and abort both sides immediately but that delivers wrong application code to the peer.
Lastly I was trying to use tracing to figure out what is happening I finally succeeded but I had to improve it somewhat.
The current log is way to crude and it does not include useful information. So I split the generic log to each event handler so we can log also some specific parts. With that following fragments was creation for solving this:
so it is clear we received 3 bytes out of promised 15400 and aborted immediately. Peer (e.g. LoopBack server) will get PEER_RECEIVE_ABORTED before completing write.
fixes #73930