-
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
HttpConnectionPool violates HttpHeaders thread-safety for CONNECT tunnels #65379
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsIn .NET 6, the creation of a connection has been decoupled from the initiating request. That is, a connection attempt may still be ongoing even after the initiating request was canceled or was served by a different connection that became available. In the case of This is a possible explanation for an issue a user hit in #61798 (comment) based on the stack trace. cc: @Strepto
|
Presumably the fix here is to make sure we capture the User-Agent from the original request before we actually put the request in the queue (and make it available for other connections to grab), is that right? |
Yes, that's right. |
Somewhat related here: We are forcing the User-Agent to be parsed here because we use TryGetValues. We should avoid this by using NonValidated here. (I thought there was an issue on this but I couldn't find it...) |
Triage: Impact on reliability with proxies, we should address it. Hopefully not too complex. |
This has been mitigated by #68115 in 7.0 - you should only hit this issue if the request message's headers are modified after it was sent. Moving to future. |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
@Edgaras91 can you please open a new issue and share how you are making HTTP calls? Is this only occurring when using a proxy? If you have a reproducible example that would also help a ton to diagnose the issue. IIS does not impact Seeing this sort of exception means multiple threads are accessing the header collection concurrently. There are a few known cases (NewRelic, CONNECT tunnels ...), but it's possible something else is happening in your scenario. |
Will this fix be backported to .NET 6? |
@madelson can you please give us some numbers on how often it happens to you? How much it impacts your service? Potentially any business impact on your company or your customers. |
Triage: Given that we might need to backport the fix to 6.0 (per discussion above - pending impact details), we should fix it in 7.0 (although the impact on 7.0 is much lower than on 6.0) to have the fix ready for potential backport to 6.0.x and to avoid servicing 7.0 if we decide in future to service 6.0. |
@madelson can you also please confirm whether the workaround mentioned in the top issue (accessing the |
@MihaZupan thanks for following up. We've deployed the workaround and are actively monitoring. The error was transient, so we want to wait a week or so before declaring victory. |
@MihaZupan I still have to do some more digging to confirm early next week, but so far it looks like the workaround may not have fixed the issue for us. |
Can you share how you implemented the workaround? We would expect that it is a 100% reliable fix for this specific issue. If it's not, it's possible you are running into a different header issue (e.g. using outdated NewRelic) that results in similar issues. |
@MihaZupan ok I think we have a false alarm; the error was in an environment that hadn't received the fix yet. I'll continue to monitor. Here's what our implementation of the workaround looks like: var transport = new HttpClientTransport(new UserAgentAccessingHandler(new HttpClientHandler
{
Proxy = webProxy,
MaxConnectionsPerServer = this._maxConnectionsPerServer.Value
}));
return new BlobServiceClient(uri, options: new() { Transport = transport });
#if NET6_0 // this will remind us to revisit this if we upgrade our .NET version!
/// <summary>
/// Workaround for .NET 6 concurrency bug (see https://github.com/dotnet/runtime/issues/65379).
/// </summary>
private sealed class UserAgentAccessingHandler : DelegatingHandler
{
private const string UserAgentHeader = "User-Agent";
public UserAgentAccessingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
_ = request.Headers.TryGetValues(UserAgentHeader, out _); // Force parsing
return base.Send(request, cancellationToken);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_ = request.Headers.TryGetValues(UserAgentHeader, out _); // Force parsing
return base.SendAsync(request, cancellationToken);
}
}
#endif I also just want to confirm that this is a flavor of the error that should be fixed here:
|
The workaround looks good. |
EDIT: thought we had a conclusion here but still looking. |
In .NET 6, the creation of a connection has been decoupled from the initiating request. That is, a connection attempt may still be ongoing even after the initiating request was canceled or was served by a different connection that became available.
In the case of
HttpConnectionKind.SslProxyTunnel
(CONNECT tunnel), we copy the original request'sUser-Agent
header to the tunnel request here. The problem is that we access the header too late.At that point, the original request's header collection could be enumerated on a different thread (served by a different connection).
Or, the
SendAsync
call associated with the request already returned, seemingly returning ownership back to the user. The user may then attempt to enumerate/inspect the headers, which would again race with the read fromEstablishProxyTunnelAsync
.This is a possible explanation for an issue a user hit in #61798 (comment) based on the stack trace.
cc: @Strepto
This specific issue can be worked around by manually forcing the parsing of the
User-Agent
header:The text was updated successfully, but these errors were encountered: