Open MihaZupan opened 2 years ago
Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.
Author: | MihaZupan |
---|---|
Assignees: | - |
Labels: | `bug`, `area-System.Net.Http` |
Milestone: | - |
I assume this is due to the change in .NET 6 that enables a different connection to service the request if one becomes available before this connection has been established?
Exactly.
In general that change would seem to have broken the notion of "InitialRequestMessage", since it's not necessarily. That's still the request that made the system decide to inject another connection, but it's not necessarily the first one to use it, or even use it at all.
Triage:
InitialRequestMessage
is used -- perhaps Obsolete it?Note that this isn't only specific to headers, but anything exposed on HttpRequestMessage
.
Headers are the most likely to show up as exceptions, but the ownership of the object is a problem for all the properties.
The user is free to observe/change the properties (Uri / headers / method / Options) after the request is finished, and may not expect those changes to happen while in ConnectCallback
.
Worst-case, a user may be reusing the request object entirely (something we don't guard against via HttpMessageInvoker
) or using it as a builder for subsequent requests (real scenario).
This could lead to the connect callback establishing a connection to an unrelated endpoint for a wrong connection pool.
Team discussion
Options:
HttpRequestMessage
InitialRequest
until first await
in the methodConnectCallback
(in combo with documenting against modifying request as in above)InitialRequest
- as soon as you access it from context, we would "lock it" for the threadNote: Also PlainTextFilter
gets the InitialRequest
- that needs to be addressed / documented as well.
We are leaning towards [3]. Perhaps either way do [6].
Continued triage:
The mitigation around headers (concurrent reads) has been merged (https://github.com/dotnet/runtime/pull/68115) and I've updated the docs to mention that request message reuse is discouraged (https://github.com/dotnet/dotnet-api-docs/pull/8224).
Moving to future.
Team triage:
InitialRequest
(with detailed explanation why). Before we do that, we will check top usages on GH (incl. gRPC - @JamesNK) ... it is the least bad solution.Can gRPC keep using it? Or could the request's property bag be provided in the callback? I need some way to pass in state to the callback from the request.
You could suppress the obsoletion warning and use it, but the question is if it behaves how you're expecting it to.
The InitialRequestMessage
you see in the callback may not be the first request on that connection. It may not even be sent using the connection the callback is establishing. It may have already been sent and completed before the callback runs, ...
What are you using the properties for in gRPC?
@JamesNK ping on Miha's question above ...
The callback is here: https://github.com/grpc/grpc-dotnet/blob/4bf9312123f8a63caa408ca74d92040e5763beb4/src/Grpc.Net.Client/Balancer/Internal/BalancerHttpHandler.cs#L77-L90
The values are set onto the request here: https://github.com/grpc/grpc-dotnet/blob/4bf9312123f8a63caa408ca74d92040e5763beb4/src/Grpc.Net.Client/Balancer/Internal/BalancerHttpHandler.cs#L107-L134
Basically, this handler changes the request URI based on the result from load balancing, and also sets some properties on the request's options. The properties in the request options are only used if the callback is run, and they're used to establish the new connection stream.
Passing read-only information to ConnectCallback through InitialRequestMessage.Options
seems to be a common practice advanced users do. To me this looks safe and I don't see any alternatives. Are there any cases where it might be problematic? If it is indeed safe and we deprecate the property, shouldn't we expose an alternative API on SocketsHttpConnectionContext
to read options?
One problematic case for Options
is if you are using a CONNECT
proxy tunnel, the request object you see in the callback is something we've created inside the connection pool, not one of your requests (and the options aren't being passed through).
If we had a different API instead of a request, I assume we would account for that and flow it through.
shouldn't we expose an alternative API on SocketsHttpConnectionContext to read options?
Do you mean literally NewApi => InitialRequestMessage.Options
, or a different way of supplying those options as well?
Using the request itself still has issues w.r.t. object lifetimes. If the caller wanted to reuse requests in between (including clearing Options
), it can't know when it's safe to do so.
One problematic case for
Options
is if you are using aCONNECT
proxy tunnel, the request object you see in the callback is something we've created inside the connection pool, not one of your requests (and the options aren't being passed through).
I think current users of InitialRequestMessage.Options
are aware of this, and they do not use proxy tunnels.
Do you mean literally
NewApi => InitialRequestMessage.Options
I originally meant this, but we can consider introducing a new ConnectionOptions
bag, if we want to solve the proxy connect problem.
If the caller wanted to reuse requests in between
Isn't this something we explicitly discourage?
By the time the callback runs, the initial request may have already been completed and returned to the user, or it is being sent by a different connection. That is, a different thread may be accessing the headers while the callback is running.
The same concern applies to SocketsHttpPlaintextStreamFilterContext.InitialRequestMessage
. If we go with obsoletion, we should obsolete both properties.
The
ConnectCallback
is provided with aSocketsHttpConnectionContext
that includes theInitialRequestMessage
. By the time the callback runs, the initial request may have already been completed and returned to the user, or it is being sent by a different connection. That is, a different thread may be accessing the headers while the callback is running.As a result, any access of
InitialRequestMessage
's headers is problematic.Related: #61798, #65379