-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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/3] NullReferenceException on cancellation #48624
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsThere is a gRPC functional test called The test:
Instead of throwing the same exception as HTTP/2 (I think is an OCE), a NullReferenceException is thrown. Stack trace:
I think the problem is a null runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Lines 1108 to 1111 in 3bc7840
|
Triage: the support for cancelation in QUIC is lacking atm, we should fix this. |
@JamesNK could you please share what exactly is done on the server and what on the client? |
Client and server code: https://github.com/grpc/grpc-dotnet/blob/9f72e70c6a47ae823bb3ddcc719f1c63bf721fc7/test/FunctionalTests/Client/CancellationTests.cs#L352-L410 I think the error came from line 396. I know you'd prefer a reproduction that doesn't involve gRPC but once I begin testing HTTP/3 with the gRPC functional tests again then I'll likely be raising a lot of issues from the tests. Recreating each issue without gRPC will take too long. I have a branch that adds HTTP/3 support to the grpc-dotnet repository but it is many months out of date. I will need to update it before you can test HTTP/3 on it - https://github.com/JamesNK/grpc-dotnet/commits/jamesnk/http3 |
@JamesNK thanks for sharing the code! |
I've tried to create an explanation about what happens with links to the code, as it ended up hard to explain only with words. What happens is: client sends a request. For the response, server sends a couple of messages and then pauses without closing the connection. Client reads these two messages and on the next read task (namely, Cancellation token source gets created here It is passed to gRPC internals in And then in the middle of client waiting on read call, this token gets cancelled Returning to gRPC internals, inside The external token Which will cancel the internal And then dispose Returning to the reading task in the test
After that, the token that is passed to our stream for reading is not user's token, but internal JFYI the method for actual reading from the steam is here And the stream is What we are passing as Content stream is runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Line 221 in 61587f4
Which on read will be calling for Http3RequestStream.ReadResponseContentAsync runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Line 1244 in 61587f4
The cancellation token is passed forward there to reading on QuicStream runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Line 1079 in 61587f4
And if the cancellation occurs, this QuicStream will be aborted with a specific runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Lines 1115 to 1116 in 61587f4
But it will already be null (most of the times I guess, as it's essentially a race condition), because runtime/src/libraries/System.Net.Http/src/System/Net/Http/HttpResponseMessage.cs Line 235 in 61587f4
which disposed stream inside (that is Http3ReadStream as I've mentioned before)
which disposed Http3RequestStream runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Line 1197 in 61587f4
which disposed QuicStream runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Line 84 in 61587f4
and set it to null runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs Line 103 in 61587f4
So what I understand happens on cancellation here
While getting rid of the exception is easy (check for null before aborting stream) I want to understand the whole scenario because it seems a bit strange to me. Why HttpResponseMessage is disposed inside cancellation callback? That itself wouldn't lead to OperationCanceledException, but to ObjectDisposedException. Would it be more logical for the disposal to happen after catching OperationCanceledException from the stream? If this disposal pattern in gRPC will be left as it is, we won't send a correct error code to the peer (i.e. it will be some generic error code, not about the request being canceled). I understand that in general we won't be able to prevent a user from calling Dispose at any, even unexpected, time, but how much do we care about this specific cancellation situation? I also don't know, whether disposing |
It is how you abort an HTTP request that has already returned a response. If the stream is in progress an abort is sent to the server. Scenarios to cancel HTTP request:
|
Fix abort on cancellation for HTTP/3 content stream, fix dispose when read was aborted by cancellation token. Fixes #48624
There is a gRPC functional test called
ServerStreaming_CancellationOnClientWhileMoveNext_CancellationSentToServer
.The test:
HttpResponseMessage
(which cancels the request)Instead of throwing the same exception as HTTP/2 (I think is an OCE), a NullReferenceException is thrown.
Stack trace:
I think the problem is a null
_connection
is called here:runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs
Lines 1108 to 1111 in 3bc7840
The text was updated successfully, but these errors were encountered: