Skip to content
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

fix issue where HTTP/1.1 connections were not being created asynchronously #56966

Merged
merged 2 commits into from
Aug 11, 2021

Conversation

geoffkizer
Copy link
Contributor

Fixes #56477 (hopefully)

@ghost
Copy link

ghost commented Aug 6, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #56477 (hopefully)

Author: geoffkizer
Assignees: -
Labels:

area-System.Net.Http

Milestone: -

@geoffkizer
Copy link
Contributor Author

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@ManickaP
Copy link
Member

ManickaP commented Aug 6, 2021

I found few more places to consider:

http11Connection = await ConstructHttp11ConnectionAsync(false, socket, stream, transportContext, request, cancellationToken).ConfigureAwait(false);

(Socket? socket, Stream stream, TransportContext? transportContext) = await ConnectAsync(request, false, cts.Token).ConfigureAwait(false);


Slightly off-topic, but why do we have bool async in H/2 and H/3 methods? They do not support sync AFAIK and we are throwing if sync and H/2 | H/3 is requested.

response = await TrySendUsingHttp3Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);

response = await TrySendUsingHttp2Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false);

@geoffkizer
Copy link
Contributor Author

I found few more places to consider:

Thanks, I will look at these tomorrow. They definitely look suspicious.

Slightly off-topic, but why do we have bool async in H/2 and H/3 methods?

I honestly don't know. You are right, there shouldn't be any non-async paths in H2 or H3. It may be a legacy from previous code that we just have never cleaned up.

@@ -461,7 +461,7 @@ private async Task AddHttp11ConnectionAsync(HttpRequestMessage request)
{
try
{
connection = await CreateHttp11ConnectionAsync(request, false, cts.Token).ConfigureAwait(false);
connection = await CreateHttp11ConnectionAsync(request, true, cts.Token).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this work for the Sync HttpClient? I did not see any AddHttp11Connection counterpart.
This is not my area but I was under impression that we want to do alway sync operations for the sync client and async for "normal" case. Having the value fixed makes me wonder....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only call site to this method is just queueing it via a Task.Run, so there won't ever be a sync caller.

However, this does raise the point that I think Geoff's previous change around connection pooling does mean that we're never creating connections synchronously when we used to, which has a couple ramifications we should consider. First, it means every socket on Unix will be in a non-blocking state, which will make the synchronous operations performed on that connection more expensive. Second, it means that some number of sync requests will be done sync-over-async as they synchronously block waiting for a connection to be available, whereas previously they would have actually been creating the connection synchronously.

@geoffkizer
Copy link
Contributor Author

geoffkizer commented Aug 8, 2021

However, this does raise the point that I think Geoff's previous change around connection pooling does mean that we're never creating connections synchronously when we used to

Yes, that's correct.

In the new approach, we don't really have a direct correlation between a connection being created and a request that will use that connection. All requests get put in the queue; any connection that becomes available will be used to handle that request. So requests that cause a new connection to be created are no longer just waiting on that connection to be created; they are waiting on any connection to become available. This benefits both sync and async usage; but as you point out, there are some downsides in the sync case specifically.

First, it means every socket on Unix will be in a non-blocking state, which will make the synchronous operations performed on that connection more expensive. Second, it means that some number of sync requests will be done sync-over-async as they synchronously block waiting for a connection to be available, whereas previously they would have actually been creating the connection synchronously.

Yep, agreed. And unfortunately I don't see great mitigations for either of these issues, short of going back to the old model. I can imagine some minor tweaks, but all of them add nontrivial complexity and I'm not sure they really help the general situation much.

Thoughts?

EDIT: Further thoughts:

I don't think that the second issue is that big of a deal in general. Once you get to steady state usage, creating a new connection isn't that common; hopefully connections are reused frequently, and only fail/timeout occasionally.

The first issue could be mitigated by creating the connection synchronously if the request that causes it to be created is a synchronous request. Assuming that you only use sync APIs, this would mean the socket would stay in sync mode. (And if you are using a mix of sync APIs and async APIs, you are inducing this problem anyway.) However, it has the downside that connection creation will now tie up another threadpool thread, since it would happen synchronously.

@geoffkizer
Copy link
Contributor Author

Slightly off-topic, but why do we have bool async in H/2 and H/3 methods? They do not support sync AFAIK and we are throwing if sync and H/2 | H/3 is requested.

Yeah, we should probably remove this. I suspect it's a legacy of refactoring and just never got cleaned up.

@geoffkizer
Copy link
Contributor Author

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@ManickaP ManickaP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks. Let get this in.
We can clean up the non-sensical async args in 7.0, they don't hurt anything here.

@geoffkizer geoffkizer merged commit ace0f64 into dotnet:main Aug 11, 2021
@karelz karelz added this to the 6.0.0 milestone Aug 17, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Sep 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Massive long running tests on Linux
5 participants