Uh oh!
There was an error while loading. Please reload this page.
Remove two async state machines for typical HTTP/1.1 request path - #58092
Conversation
ghost
commented
Aug 25, 2021
Tagging subscribers to this area: @dotnet/ncl Issue DetailsRather than having a dedicated async method for SendAndProcessAltSvcAsync, we can just have the sole call site call DetermineVersionAndSendAsync and ProcessAltSvc. And in DetermineVersionAndSendAsync, special-case the default configuration for SocketsHttpHandler that will result in HTTP/1.1 messages being sent, such that we avoid all the tests for HTTP/2 and HTTP/3 and the async state machine for DetermineVersionAndSendAsync when HTTP/1.1 is being requested and used. This doesn't entirely get us back to the allocation profile of .NET 5, but it's much closer.
I also noticed an unused parameter to the HTTP/2 and HTTP/3 try send async methods, so I removed those as well, removing those parameters from the corresponding state machines. Fixes #57977
|
geoffkizer
commented
Aug 25, 2021
Should we keep #57977 open and consider other improvements, or do we think this is good for 6.0? |
stephentoub
commented
Aug 25, 2021
The changes in this PR seemed reasonable to me even without a goal of reducing state machines, i.e. I don't feel like we're sacrificing layering or factoring or anything like that. Beyond these two, though, it seems like we would have to make such choices, which makes me uncomfortable. I think we can live with the resulting 10% allocation increase over .NET 5 for this raw, do-as-little-as-possible benchmark. The moment you start doing anything more real, this won't matter. And if you start touching headers, .NET 6 is already way better. That said, if you had ideas for getting rid of the SendUsingHttp11Async state machine (it's basically what remains now), I'd be interested. There's also the option of using the pooling async method builder, but I think it's late in the game at this point to be exploring that here. |
geoffkizer
commented
Aug 25, 2021
We could just inline GetHttp11ConnectionAsync into SendUsingHttp11Async. |
stephentoub
commented
Aug 25, 2021
Would you be ok with that? That felt like something you were trying to keep separate. But if you're cool with it, I'll add it to this PR. |
geoffkizer
commented
Aug 25, 2021
I think it's fine. SendUsingHttp11Async is not doing much currently aside from calling GetHttp11ConnectionAsync and then actually doing the send on the connection. |
stephentoub
commented
Aug 25, 2021
Oh, wait, that's not going to help. GetHttp11ConnectionAsync typically completes synchronously. It's SendUsingHttp11Async itself we'd need to inline, into SendWithRetryAsync. |
geoffkizer
commented
Aug 25, 2021
That doesn't seem crazy either. |
Uh oh!
There was an error while loading. Please reload this page.
stephentoub
commented
Aug 25, 2021
Inlining it would look like: |
geoffkizer
commented
Aug 26, 2021
I think so, yeah. It doesn't seem that ugly to me. I kind of wish we had a better name than "SendWithRetryAsync" now, since it's also doing the version determination... but I don't have a great suggestion. It's basically the main Send routine now (the only thing that happens before this is proxy auth) so perhaps we could call it SendAsyncCore or similar? I dunno. Thoughts? Does this give a noticeable win vs previous changes? I imagine we could do something similar here to get rid of a state machine for each of HTTP2 and HTTP3, right? Not super important at this point, but perhaps something to consider in the future. That said, re HTTP3, I kind of want to refactor that a bit. Seems like some logic that's currently in TrySendUsingHttp3Async could instead be in GetHttp3ConnectionAsync, and the 421 retry logic could just be incorporated into our general retry logic. But none of that needs to happen for 6.0. |
stephentoub
commented
Aug 26, 2021
Ok, I think this is reasonable. @geoffkizer please take another look. At this point, the simple scenario previously benchmarked is only 48 bytes more than .NET 5 (I could easily get it to 32 bytes by doing something unnatural I chose not to do).
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
geoffkizer
left a comment
There was a problem hiding this comment.
One nit re the assert, otherwise LGTM
geoffkizer
commented
Aug 26, 2021
Also, let's run outerloop before merging. |
geoffkizer
commented
Aug 26, 2021
Is the plan to backport to 6.0 as is? If so I'm fine with that (assuming clean outerloop run) |
stephentoub
commented
Aug 26, 2021
/azp list |
stephentoub
commented
Aug 26, 2021
/azp run runtime-libraries-coreclr outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
stephentoub
commented
Aug 26, 2021
I'd like to, yes. I feel a lot better about an only ~3% increase in allocation than an ~33% increase in allocation, even for a minimal benchmark. |
stephentoub
commented
Aug 27, 2021
/backport to release/6.0 |
Started backporting to release/6.0: https://github.com/dotnet/runtime/actions/runs/1173957232 |
Rather than having a dedicated async method for SendAndProcessAltSvcAsync, we can just have the sole call site call DetermineVersionAndSendAsync and ProcessAltSvc. And in DetermineVersionAndSendAsync, special-case the default configuration for SocketsHttpHandler that will result in HTTP/1.1 messages being sent, such that we avoid all the tests for HTTP/2 and HTTP/3 and the async state machine for DetermineVersionAndSendAsync when HTTP/1.1 is being requested and used.
This doesn't entirely get us back to the allocation profile of .NET 5, but it's much closer.
I also noticed an unused parameter to the HTTP/2 and HTTP/3 try send async methods, so I removed those as well, removing those parameters from the corresponding state machines.
Best reviewed without whitespace diffing: https://github.com/dotnet/runtime/pull/58092/files?w=1
Fixes#57977