From f3c586651a6c0667c6c312fa0c2fa84a4260b458 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 24 Aug 2021 20:36:26 -0400 Subject: [PATCH 1/5] Remove two async state machines for typical HTTP/1.1 request path --- .../SocketsHttpHandler/HttpConnectionPool.cs | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 787b89c8bd7a29..60747783d4489d 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -969,51 +969,60 @@ private async ValueTask SendUsingHttp11Async(HttpRequestMes } } - private async ValueTask DetermineVersionAndSendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) + private ValueTask DetermineVersionAndSendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) { - HttpResponseMessage? response; + // The default configuration of SocketsHttpHandler and HttpRequestMessage has requests use HTTP/1.1. + // Special-case it to prioritize it and avoid an extra layer of async state machine for this common case. + if (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrHigher && + request.Version.Major == 1) + { + return SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken); + } - if (IsHttp3Supported()) + return Core(request, async, doRequestAuth, cancellationToken); + + async ValueTask Core(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) { - response = await TrySendUsingHttp3Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + HttpResponseMessage? response; + + if (IsHttp3Supported()) + { + response = await TrySendUsingHttp3Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + if (response is not null) + { + return response; + } + } + + // We cannot use HTTP/3. Do not continue if downgrade is not allowed. + if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) + { + throw GetVersionException(request, 3); + } + + response = await TrySendUsingHttp2Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); if (response is not null) { return response; } - } - - // We cannot use HTTP/3. Do not continue if downgrade is not allowed. - if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - throw GetVersionException(request, 3); - } - response = await TrySendUsingHttp2Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); - if (response is not null) - { - return response; - } + // We cannot use HTTP/2. Do not continue if downgrade is not allowed. + if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) + { + throw GetVersionException(request, 2); + } - // We cannot use HTTP/2. Do not continue if downgrade is not allowed. - if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - throw GetVersionException(request, 2); + return await SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); } - - return await SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); } - private async ValueTask SendAndProcessAltSvcAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) + /// Check for the Alt-Svc header, to upgrade to HTTP/3. + private void ProcessAltSvc(HttpResponseMessage response) { - HttpResponseMessage response = await DetermineVersionAndSendAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); - - // Check for the Alt-Svc header, to upgrade to HTTP/3. if (_altSvcEnabled && response.Headers.TryGetValues(KnownHeaders.AltSvc.Descriptor, out IEnumerable? altSvcHeaderValues)) { HandleAltSvc(altSvcHeaderValues, response.Headers.Age); } - - return response; } public async ValueTask SendWithRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) @@ -1024,7 +1033,9 @@ public async ValueTask SendWithRetryAsync(HttpRequestMessag // Loop on connection failures (or other problems like version downgrade) and retry if possible. try { - return await SendAndProcessAltSvcAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + HttpResponseMessage response = await DetermineVersionAndSendAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + ProcessAltSvc(response); + return response; } catch (HttpRequestException e) when (e.AllowRetry == RequestRetryType.RetryOnConnectionFailure) { From c50c663bada9ef341e50ceb501dd0c6d5c2df645 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 24 Aug 2021 21:13:35 -0400 Subject: [PATCH 2/5] Remove unused doRequestAuth parameter for HTTP/2 and HTTP/3 --- .../Net/Http/SocketsHttpHandler/HttpConnectionPool.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 60747783d4489d..1bd85a15bd8a28 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -887,7 +887,7 @@ private async ValueTask GetHttp3ConnectionAsync(HttpRequestMess [SupportedOSPlatform("windows")] [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] - private async ValueTask TrySendUsingHttp3Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) + private async ValueTask TrySendUsingHttp3Async(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { if (_http3Enabled && (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure))) { @@ -933,7 +933,7 @@ private async ValueTask GetHttp3ConnectionAsync(HttpRequestMess } // Returns null if HTTP2 cannot be used. - private async ValueTask TrySendUsingHttp2Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) + private async ValueTask TrySendUsingHttp2Async(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { // Send using HTTP/2 if we can. if (_http2Enabled && (request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) && @@ -987,7 +987,7 @@ async ValueTask Core(HttpRequestMessage request, bool async if (IsHttp3Supported()) { - response = await TrySendUsingHttp3Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + response = await TrySendUsingHttp3Async(request, async, cancellationToken).ConfigureAwait(false); if (response is not null) { return response; @@ -1000,7 +1000,7 @@ async ValueTask Core(HttpRequestMessage request, bool async throw GetVersionException(request, 3); } - response = await TrySendUsingHttp2Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + response = await TrySendUsingHttp2Async(request, async, cancellationToken).ConfigureAwait(false); if (response is not null) { return response; From 364391fa3517a88b27031eae5d02e9661b257777 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 25 Aug 2021 11:04:17 -0400 Subject: [PATCH 3/5] Inline HTTP/1.x handling into SendWithRetryAsync --- .../SocketsHttpHandler/HttpConnectionPool.cs | 108 +++++++----------- 1 file changed, 42 insertions(+), 66 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 1bd85a15bd8a28..71ead4a0f17283 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -929,6 +929,12 @@ private async ValueTask GetHttp3ConnectionAsync(HttpRequestMess } } + // We cannot use HTTP/3. Do not continue if downgrade is not allowed. + if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) + { + throw GetVersionException(request, 3); + } + return null; } @@ -941,79 +947,21 @@ private async ValueTask GetHttp3ConnectionAsync(HttpRequestMess (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) { Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); - if (connection is null) + if (connection is not null) { - Debug.Assert(!_http2Enabled); - return null; + return await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false); } - return await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false); + Debug.Assert(!_http2Enabled); } - return null; - } - - private async ValueTask SendUsingHttp11Async(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) - { - HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); - - // In case we are doing Windows (i.e. connection-based) auth, we need to ensure that we hold on to this specific connection while auth is underway. - connection.Acquire(); - try + // We cannot use HTTP/2. Do not continue if downgrade is not allowed. + if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) { - return await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + throw GetVersionException(request, 2); } - finally - { - connection.Release(); - } - } - private ValueTask DetermineVersionAndSendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) - { - // The default configuration of SocketsHttpHandler and HttpRequestMessage has requests use HTTP/1.1. - // Special-case it to prioritize it and avoid an extra layer of async state machine for this common case. - if (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrHigher && - request.Version.Major == 1) - { - return SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken); - } - - return Core(request, async, doRequestAuth, cancellationToken); - - async ValueTask Core(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) - { - HttpResponseMessage? response; - - if (IsHttp3Supported()) - { - response = await TrySendUsingHttp3Async(request, async, cancellationToken).ConfigureAwait(false); - if (response is not null) - { - return response; - } - } - - // We cannot use HTTP/3. Do not continue if downgrade is not allowed. - if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - throw GetVersionException(request, 3); - } - - response = await TrySendUsingHttp2Async(request, async, cancellationToken).ConfigureAwait(false); - if (response is not null) - { - return response; - } - - // We cannot use HTTP/2. Do not continue if downgrade is not allowed. - if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - throw GetVersionException(request, 2); - } - - return await SendUsingHttp11Async(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); - } + return null; } /// Check for the Alt-Svc header, to upgrade to HTTP/3. @@ -1033,7 +981,35 @@ public async ValueTask SendWithRetryAsync(HttpRequestMessag // Loop on connection failures (or other problems like version downgrade) and retry if possible. try { - HttpResponseMessage response = await DetermineVersionAndSendAsync(request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + HttpResponseMessage? response = null; + + // HTTP/3 + if (IsHttp3Supported()) + { + response = await TrySendUsingHttp3Async(request, async, cancellationToken).ConfigureAwait(false); + } + + if (response is null) + { + // HTTP/2 + response = await TrySendUsingHttp2Async(request, async, cancellationToken).ConfigureAwait(false); + + if (response is null) + { + // HTTP/1.x + HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); + connection.Acquire(); // In case we are doing Windows (i.e. connection-based) auth, we need to ensure that we hold on to this specific connection while auth is underway. + try + { + response = await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth, cancellationToken).ConfigureAwait(false); + } + finally + { + connection.Release(); + } + } + } + ProcessAltSvc(response); return response; } From 4dd8ebed61ab67683255d49c61dfa192ec02139c Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 25 Aug 2021 22:07:56 -0400 Subject: [PATCH 4/5] Inline HTTP/2 as well --- .../AuthenticationHelper.cs | 2 +- .../SocketsHttpHandler/HttpConnectionPool.cs | 131 ++++++++---------- 2 files changed, 60 insertions(+), 73 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs index 88b3f64d0ec7a9..639e0367ba5aa7 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.cs @@ -206,7 +206,7 @@ private static async ValueTask TrySetDigestAuthToken(HttpRequestMessage re private static ValueTask InnerSendAsync(HttpRequestMessage request, bool async, bool isProxyAuth, bool doRequestAuth, HttpConnectionPool pool, CancellationToken cancellationToken) { return isProxyAuth ? - pool.SendWithRetryAsync(request, async, doRequestAuth, cancellationToken) : + pool.SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken) : pool.SendWithProxyAuthAsync(request, async, doRequestAuth, cancellationToken); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 71ead4a0f17283..c16e66dde5ec53 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -413,11 +413,12 @@ private object SyncObj // we will remove it from the list of available connections, if it is present there. // If not, then it must be unavailable at the moment; we will detect this and ensure it is not added back to the available pool. - private static HttpRequestException GetVersionException(HttpRequestMessage request, int desiredVersion) + [DoesNotReturn] + private static void ThrowGetVersionException(HttpRequestMessage request, int desiredVersion) { Debug.Assert(desiredVersion == 2 || desiredVersion == 3); - return new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion)); + throw new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion)); } private bool CheckExpirationOnGet(HttpConnectionBase connection) @@ -889,79 +890,42 @@ private async ValueTask GetHttp3ConnectionAsync(HttpRequestMess [SupportedOSPlatform("macos")] private async ValueTask TrySendUsingHttp3Async(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { - if (_http3Enabled && (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure))) + // Loop in case we get a 421 and need to send the request to a different authority. + while (true) { - // Loop in case we get a 421 and need to send the request to a different authority. - while (true) - { - HttpAuthority? authority = _http3Authority; - - // If H3 is explicitly requested, assume prenegotiated H3. - if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - authority = authority ?? _originAuthority; - } + HttpAuthority? authority = _http3Authority; - if (authority == null) - { - break; - } - - if (IsAltSvcBlocked(authority)) - { - throw GetVersionException(request, 3); - } - - Http3Connection connection = await GetHttp3ConnectionAsync(request, authority, cancellationToken).ConfigureAwait(false); - HttpResponseMessage response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false); - - // If an Alt-Svc authority returns 421, it means it can't actually handle the request. - // An authority is supposed to be able to handle ALL requests to the origin, so this is a server bug. - // In this case, we blocklist the authority and retry the request at the origin. - if (response.StatusCode == HttpStatusCode.MisdirectedRequest && connection.Authority != _originAuthority) - { - response.Dispose(); - BlocklistAuthority(connection.Authority); - continue; - } + // If H3 is explicitly requested, assume prenegotiated H3. + if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) + { + authority ??= _originAuthority; + } - return response; + if (authority == null) + { + return null; } - } - // We cannot use HTTP/3. Do not continue if downgrade is not allowed. - if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - throw GetVersionException(request, 3); - } + if (IsAltSvcBlocked(authority)) + { + ThrowGetVersionException(request, 3); + } - return null; - } + Http3Connection connection = await GetHttp3ConnectionAsync(request, authority, cancellationToken).ConfigureAwait(false); + HttpResponseMessage response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false); - // Returns null if HTTP2 cannot be used. - private async ValueTask TrySendUsingHttp2Async(HttpRequestMessage request, bool async, CancellationToken cancellationToken) - { - // Send using HTTP/2 if we can. - if (_http2Enabled && (request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) && - // If the connection is not secured and downgrade is possible, prefer HTTP/1.1. - (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) - { - Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); - if (connection is not null) + // If an Alt-Svc authority returns 421, it means it can't actually handle the request. + // An authority is supposed to be able to handle ALL requests to the origin, so this is a server bug. + // In this case, we blocklist the authority and retry the request at the origin. + if (response.StatusCode == HttpStatusCode.MisdirectedRequest && connection.Authority != _originAuthority) { - return await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false); + response.Dispose(); + BlocklistAuthority(connection.Authority); + continue; } - Debug.Assert(!_http2Enabled); + return response; } - - // We cannot use HTTP/2. Do not continue if downgrade is not allowed. - if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) - { - throw GetVersionException(request, 2); - } - - return null; } /// Check for the Alt-Svc header, to upgrade to HTTP/3. @@ -973,30 +937,53 @@ private void ProcessAltSvc(HttpResponseMessage response) } } - public async ValueTask SendWithRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) + public async ValueTask SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) { + // Loop on connection failures (or other problems like version downgrade) and retry if possible. int retryCount = 0; while (true) { - // Loop on connection failures (or other problems like version downgrade) and retry if possible. try { HttpResponseMessage? response = null; - // HTTP/3 - if (IsHttp3Supported()) + // Use HTTP/3 if possible. + if (IsHttp3Supported() && // guard to enable trimming HTTP/3 support + _http3Enabled && + (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure))) { response = await TrySendUsingHttp3Async(request, async, cancellationToken).ConfigureAwait(false); } if (response is null) { - // HTTP/2 - response = await TrySendUsingHttp2Async(request, async, cancellationToken).ConfigureAwait(false); + // We could not use HTTP/3. Do not continue if downgrade is not allowed. + if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) + { + ThrowGetVersionException(request, 3); + } + + // Use HTTP/2 if possible. + if (_http2Enabled && + (request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure)) && + (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) // prefer HTTP/1.1 if connection is not secured and downgrade is possible + { + Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); + if (connection is not null) + { + response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false); + } + } if (response is null) { - // HTTP/1.x + // We could not use HTTP/2. Do not continue if downgrade is not allowed. + if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) + { + ThrowGetVersionException(request, 2); + } + + // Use HTTP/1.x. HttpConnection connection = await GetHttp11ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); connection.Acquire(); // In case we are doing Windows (i.e. connection-based) auth, we need to ensure that we hold on to this specific connection while auth is underway. try @@ -1319,7 +1306,7 @@ public ValueTask SendWithProxyAuthAsync(HttpRequestMessage return AuthenticationHelper.SendWithProxyAuthAsync(request, _proxyUri!, async, ProxyCredentials, doRequestAuth, this, cancellationToken); } - return SendWithRetryAsync(request, async, doRequestAuth, cancellationToken); + return SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken); } public ValueTask SendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) From e9e96f0daf8d43439bdfda694f6a1b4a7853f130 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 26 Aug 2021 15:01:24 -0400 Subject: [PATCH 5/5] Add back assert --- .../src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index c16e66dde5ec53..2879d37cb405c0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -969,6 +969,7 @@ public async ValueTask SendWithVersionDetectionAndRetryAsyn (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure)) // prefer HTTP/1.1 if connection is not secured and downgrade is possible { Http2Connection? connection = await GetHttp2ConnectionAsync(request, async, cancellationToken).ConfigureAwait(false); + Debug.Assert(connection is not null || !_http2Enabled); if (connection is not null) { response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(false);