< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 1837
Coverable lines: 1837
Total lines: 3255
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 867
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: .cctor()100%110%
File 1: .ctor(...)0%49490%
File 1: ConstructSslOptions(...)0%12120%
File 1: SendAsync(...)0%12120%
File 1: SendWithProxyAuthAsync(...)0%440%
File 1: SendWithNtConnectionAuthAsync(...)0%440%
File 1: SendWithNtProxyAuthAsync(...)0%440%
File 1: SendWithVersionDetectionAndRetryAsync(...)0%74740%
File 1: ConnectAsync(...)0%29290%
File 1: GetRemoteEndPoint(System.IO.Stream)0%440%
File 1: ConnectToTcpHostAsync(...)0%14140%
File 1: GetSslOptionsForRequest(...)0%10100%
File 1: ApplyPlaintextFilterAsync(...)0%880%
File 1: EstablishProxyTunnelAsync(...)0%440%
File 1: EstablishSocksTunnel(...)100%110%
File 1: GetConnectTimeoutCancellationTokenSource(...)0%220%
File 1: CreateConnectTimeoutException(...)100%110%
File 1: ThrowGetVersionException(...)0%660%
File 1: CanFallBackToHttp11(...)0%220%
File 1: CheckExpirationOnGet(...)0%440%
File 1: CheckExpirationOnReturn(...)0%660%
File 1: EvaluateConnectionsForEviction()0%20200%
File 1: Dispose()0%22220%
File 1: CleanCacheAndDisposeIfUnused()0%20200%
File 1: ToString()0%10100%
File 1: Trace(...)100%110%
File 2: .ctor(...)100%110%
File 2: TryGetPooledHttp11Connection(...)0%12120%
File 2: ProcessHttp11RequestQueue(...)0%30300%
File 2: CheckForHttp11ConnectionInjection()0%880%
File 2: InjectNewHttp11ConnectionAsync(...)0%10100%
File 2: CreateHttp11ConnectionAsync(...)100%110%
File 2: ConstructHttp11ConnectionAsync(...)100%110%
File 2: HandleHttp11ConnectionFailure(...)0%440%
File 2: RecycleHttp11Connection(...)0%440%
File 2: AddNewHttp11Connection(...)0%880%
File 2: ReturnHttp11Connection(...)0%440%
File 2: InvalidateHttp11Connection(...)0%220%
File 2: ScavengeHttp11ConnectionStack(...)0%880%
File 3: TryGetPooledHttp2Connection(...)0%31310%
File 3: CheckForHttp2ConnectionInjection()0%16160%
File 3: InjectNewHttp2ConnectionAsync(...)0%16160%
File 3: ConstructHttp2ConnectionAsync(...)0%440%
File 3: HandleHttp2ConnectionFailure(...)0%220%
File 3: OnSessionAuthenticationChallengeSeen()100%110%
File 3: HandleHttp11Downgrade(...)0%12120%
File 3: ReturnHttp2Connection(...)0%54540%
File 3: DisableHttp2Connection(...)0%220%
File 3: DisableHttp2ConnectionAsync(System.Net.Http.Http2Connection)0%880%
File 3: InvalidateHttp2Connection(...)0%880%
File 3: AddHttp2ConnectionForHeartBeat(...)0%440%
File 3: RemoveHttp2ConnectionFromHeartBeat(...)0%440%
File 3: HeartBeat()0%10100%
File 3: ScavengeHttp2ConnectionList(...)0%16160%
File 4: .ctor(...)100%110%
File 4: TrySendUsingHttp3Async(...)0%14140%
File 4: TryGetPooledHttp3Connection(...)0%22220%
File 4: CheckForHttp3ConnectionInjection()0%16160%
File 4: InjectNewHttp3ConnectionAsync(...)0%32320%
File 4: HandleHttp3ConnectionFailure(...)0%440%
File 4: ReturnHttp3Connection(...)0%52520%
File 4: DisableHttp3Connection(...)0%220%
File 4: DisableHttp3ConnectionAsync(System.Net.Http.Http3Connection)0%880%
File 4: InvalidateHttp3Connection(...)0%880%
File 4: ScavengeHttp3ConnectionList(...)0%16160%
File 4: TryGetHttp3Authority(...)0%10100%
File 4: ProcessAltSvc(...)0%440%
File 4: HandleAltSvc(...)0%38380%
File 4: ExpireAltSvcAuthority()100%110%
File 4: IsAltSvcBlocked(...)0%220%
File 4: BlocklistAuthority(...)0%20200%
File 4: OnNetworkChanged()0%880%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.IO;
 8using System.Net.Http.Headers;
 9using System.Net.Http.HPack;
 10using System.Net.Http.QPack;
 11using System.Net.Quic;
 12using System.Net.Security;
 13using System.Net.Sockets;
 14using System.Runtime.CompilerServices;
 15using System.Runtime.ExceptionServices;
 16using System.Runtime.InteropServices;
 17using System.Security.Authentication;
 18using System.Text;
 19using System.Threading;
 20using System.Threading.Tasks;
 21
 22namespace System.Net.Http
 23{
 24    /// <summary>Provides a pool of connections to the same endpoint.</summary>
 25    internal sealed partial class HttpConnectionPool : IDisposable
 26    {
 27        /// <summary>The maximum number of times to retry a request after a failure on an established connection.</summa
 28        private const int MaxConnectionFailureRetries = 3;
 29        public const int DefaultHttpPort = 80;
 30        public const int DefaultHttpsPort = 443;
 31
 032        private static readonly List<SslApplicationProtocol> s_http3ApplicationProtocols = new List<SslApplicationProtoc
 033        private static readonly List<SslApplicationProtocol> s_http2ApplicationProtocols = new List<SslApplicationProtoc
 034        private static readonly List<SslApplicationProtocol> s_http2OnlyApplicationProtocols = new List<SslApplicationPr
 35
 36        private readonly HttpConnectionPoolManager _poolManager;
 37        private readonly HttpConnectionKind _kind;
 38        private readonly Uri? _proxyUri;
 39        private readonly string? _telemetryServerAddress;
 40
 41        /// <summary>The origin authority used to construct the <see cref="HttpConnectionPool"/>.</summary>
 42        private readonly HttpAuthority _originAuthority;
 43
 44        /// <summary>The User-Agent header to use when creating a CONNECT tunnel.</summary>
 45        private string? _connectTunnelUserAgent;
 46
 47        // These settings are advertised by the server via SETTINGS_MAX_HEADER_LIST_SIZE and SETTINGS_MAX_FIELD_SECTION_
 48        // If we had previous connections to the same host in this pool, memorize the last value seen.
 49        // This value is used as an initial value for new connections before they have a chance to observe the SETTINGS 
 50        // Doing so avoids immediately exceeding the server limit on the first request, potentially causing the connecti
 51        // 0 means there were no previous connections, or they hadn't advertised this limit.
 52        // There is no need to lock when updating these values - we're only interested in saving _a_ value, not necessar
 53        internal uint _lastSeenHttp2MaxHeaderListSize;
 54        internal uint _lastSeenHttp3MaxHeaderListSize;
 55
 56        // Same as the above, but for SETTINGS_MAX_CONCURRENT_STREAMS.
 57        // Unlike the values above, this one starts out at SocketsHttpHandler.InitialHttp2MaxConcurrentStreams,
 58        // and we only ever memorize server-advertised values that are lower than that. That is, the setting
 59        // acts as the upper bound for what every new connection starts with.
 60        internal uint _lastSeenHttp2MaxConcurrentStreams;
 61
 62        /// <summary>Options specialized and cached for this pool and its key.</summary>
 63        private readonly SslClientAuthenticationOptions? _sslOptionsHttp11;
 64        private readonly SslClientAuthenticationOptions? _sslOptionsHttp2;
 65        private readonly SslClientAuthenticationOptions? _sslOptionsHttp2Only;
 66        private SslClientAuthenticationOptions? _sslOptionsHttp3;
 67        private readonly SslClientAuthenticationOptions? _sslOptionsProxy;
 68
 69        private readonly PreAuthCredentialCache? _preAuthCredentials;
 70
 71        /// <summary>Whether the pool has been used since the last time a cleanup occurred.</summary>
 072        private bool _usedSinceLastCleanup = true;
 73        /// <summary>Whether the pool has been disposed.</summary>
 74        private bool _disposed;
 75
 76        /// <summary>Initializes the pool.</summary>
 77        /// <param name="poolManager">The manager associated with this pool.</param>
 78        /// <param name="kind">The kind of HTTP connections stored in this pool.</param>
 79        /// <param name="host">The host with which this pool is associated.</param>
 80        /// <param name="port">The port with which this pool is associated.</param>
 81        /// <param name="sslHostName">The SSL host with which this pool is associated.</param>
 82        /// <param name="proxyUri">The proxy this pool targets (optional).</param>
 83        /// <param name="telemetryServerAddress">The value of the 'server.address' tag to be emitted by Metrics and Dist
 084        public HttpConnectionPool(HttpConnectionPoolManager poolManager, HttpConnectionKind kind, string? host, int port
 085        {
 086            _poolManager = poolManager;
 087            _kind = kind;
 088            _proxyUri = proxyUri;
 089            _maxHttp11Connections = Settings._maxConnectionsPerServer;
 090            _telemetryServerAddress = telemetryServerAddress;
 091            _lastSeenHttp2MaxConcurrentStreams = (uint)Settings._initialHttp2MaxConcurrentStreams;
 92
 93            // The only case where 'host' will not be set is if this is a Proxy connection pool. In that case the
 94            // connection targets the proxy itself, so use the proxy's host and port for the origin authority.
 095            Debug.Assert(host is not null || (kind == HttpConnectionKind.Proxy && proxyUri is not null));
 096            _originAuthority = host is not null
 097                ? new HttpAuthority(host, port)
 098                : new HttpAuthority(proxyUri!.IdnHost, proxyUri.Port);
 99
 0100            _http2Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version20;
 101
 0102            if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3)
 0103            {
 0104                _http3Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version30;
 0105            }
 106
 0107            switch (kind)
 108            {
 109                case HttpConnectionKind.Http:
 0110                    Debug.Assert(host != null);
 0111                    Debug.Assert(port != 0);
 0112                    Debug.Assert(sslHostName == null);
 0113                    Debug.Assert(proxyUri == null);
 114
 0115                    _http3Enabled = false;
 0116                    break;
 117
 118                case HttpConnectionKind.Https:
 0119                    Debug.Assert(host != null);
 0120                    Debug.Assert(port != 0);
 0121                    Debug.Assert(sslHostName != null);
 0122                    Debug.Assert(proxyUri == null);
 0123                    break;
 124
 125                case HttpConnectionKind.Proxy:
 0126                    Debug.Assert(host == null);
 0127                    Debug.Assert(port == 0);
 0128                    Debug.Assert(sslHostName == null);
 0129                    Debug.Assert(proxyUri != null);
 130
 0131                    _http2Enabled = false;
 0132                    _http3Enabled = false;
 0133                    break;
 134
 135                case HttpConnectionKind.ProxyTunnel:
 0136                    Debug.Assert(host != null);
 0137                    Debug.Assert(port != 0);
 0138                    Debug.Assert(sslHostName == null);
 0139                    Debug.Assert(proxyUri != null);
 140
 141                    // A CONNECT tunnel to the origin server behaves like a direct connection once established,
 142                    // so cleartext HTTP/2 (h2c) can be used over it. HTTP/1.1 WebSockets keep working because
 143                    // the WebSocket upgrade request uses HTTP/1.1 and never attempts HTTP/2.
 0144                    _http3Enabled = false;
 0145                    break;
 146
 147                case HttpConnectionKind.SslProxyTunnel:
 0148                    Debug.Assert(host != null);
 0149                    Debug.Assert(port != 0);
 0150                    Debug.Assert(sslHostName != null);
 0151                    Debug.Assert(proxyUri != null);
 152
 0153                    _http3Enabled = false; // TODO: how do we tunnel HTTP3?
 0154                    break;
 155
 156                case HttpConnectionKind.ProxyConnect:
 0157                    Debug.Assert(host != null);
 0158                    Debug.Assert(port != 0);
 0159                    Debug.Assert(sslHostName == null);
 0160                    Debug.Assert(proxyUri != null);
 161
 162                    // Don't enforce the max connections limit on proxy tunnels; this would mean that connections to dif
 163                    // would compete for the same limited number of connections.
 164                    // We will still enforce this limit on the user of the tunnel (i.e. ProxyTunnel or SslProxyTunnel).
 0165                    _maxHttp11Connections = int.MaxValue;
 166
 0167                    _http2Enabled = false;
 0168                    _http3Enabled = false;
 0169                    break;
 170
 171                case HttpConnectionKind.SocksTunnel:
 172                case HttpConnectionKind.SslSocksTunnel:
 0173                    Debug.Assert(host != null);
 0174                    Debug.Assert(port != 0);
 0175                    Debug.Assert(proxyUri != null);
 176
 0177                    _http3Enabled = false; // TODO: SOCKS supports UDP and may be used for HTTP3
 0178                    break;
 179
 180                default:
 0181                    Debug.Fail("Unknown HttpConnectionKind in HttpConnectionPool.ctor");
 182                    break;
 183            }
 184
 0185            if (!_http3Enabled)
 0186            {
 187                // Avoid parsing Alt-Svc headers if they won't be used.
 0188                _altSvcEnabled = false;
 0189            }
 190
 0191            string? hostHeader = null;
 0192            if (host is not null)
 0193            {
 194                // Precalculate ASCII bytes for Host header
 195                // Note that if _host is null, this is a (non-tunneled) proxy connection, and we can't cache the hostnam
 0196                hostHeader = IsDefaultPort
 0197                    ? _originAuthority.HostValue
 0198                    : $"{_originAuthority.HostValue}:{_originAuthority.Port}";
 199
 200                // Note the IDN hostname should always be ASCII, since it's already been IDNA encoded.
 0201                byte[] hostHeaderLine = new byte[6 + hostHeader.Length + 2]; // Host: foo\r\n
 0202                "Host: "u8.CopyTo(hostHeaderLine);
 0203                Encoding.ASCII.GetBytes(hostHeader, hostHeaderLine.AsSpan(6));
 0204                hostHeaderLine[^2] = (byte)'\r';
 0205                hostHeaderLine[^1] = (byte)'\n';
 0206                _hostHeaderLineBytes = hostHeaderLine;
 207
 0208                Debug.Assert(Encoding.ASCII.GetString(_hostHeaderLineBytes) == $"Host: {hostHeader}\r\n");
 0209            }
 210
 0211            if (sslHostName != null)
 0212            {
 0213                _sslOptionsHttp11 = ConstructSslOptions(poolManager, sslHostName);
 0214                _sslOptionsHttp11.ApplicationProtocols = null;
 215
 0216                if (_http2Enabled)
 0217                {
 0218                    _sslOptionsHttp2 = ConstructSslOptions(poolManager, sslHostName);
 0219                    _sslOptionsHttp2.ApplicationProtocols = s_http2ApplicationProtocols;
 0220                    _sslOptionsHttp2Only = ConstructSslOptions(poolManager, sslHostName);
 0221                    _sslOptionsHttp2Only.ApplicationProtocols = s_http2OnlyApplicationProtocols;
 222
 223                    // Note:
 224                    // The HTTP/2 specification states:
 225                    //   "A deployment of HTTP/2 over TLS 1.2 MUST disable renegotiation.
 226                    //    An endpoint MUST treat a TLS renegotiation as a connection error (Section 5.4.1)
 227                    //    of type PROTOCOL_ERROR."
 228                    // which suggests we should do:
 229                    //   _sslOptionsHttp2.AllowRenegotiation = false;
 230                    // However, if AllowRenegotiation is set to false, that will also prevent
 231                    // renegotation if the server denies the HTTP/2 request and causes a
 232                    // downgrade to HTTP/1.1, and the current APIs don't provide a mechanism
 233                    // by which AllowRenegotiation could be set back to true in that case.
 234                    // For now, if an HTTP/2 server erroneously issues a renegotiation, we'll
 235                    // allow it.
 0236                }
 0237            }
 238
 0239            if (hostHeader is not null)
 0240            {
 0241                if (_http2Enabled)
 0242                {
 0243                    _http2EncodedAuthorityHostHeader = HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingToAllocatedAr
 0244                }
 245
 0246                if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3 && _http3Enabled)
 0247                {
 0248                    _http3EncodedAuthorityHostHeader = QPackEncoder.EncodeLiteralHeaderFieldWithStaticNameReferenceToArr
 0249                }
 0250            }
 251
 252            // Set up for PreAuthenticate.  Access to this cache is guarded by a lock on the cache itself.
 0253            if (_poolManager.Settings._preAuthenticate)
 0254            {
 0255                _preAuthCredentials = new PreAuthCredentialCache();
 0256            }
 257
 0258            _http11RequestQueue = new RequestQueue<HttpConnection>();
 0259            if (_http2Enabled)
 0260            {
 0261                _http2RequestQueue = new RequestQueue<Http2Connection?>();
 0262            }
 0263            if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3 && _http3Enabled)
 0264            {
 0265                _http3RequestQueue = new RequestQueue<Http3Connection?>();
 0266            }
 267
 0268            if (_proxyUri != null && HttpUtilities.IsSupportedSecureScheme(_proxyUri.Scheme))
 0269            {
 0270                _sslOptionsProxy = ConstructSslOptions(poolManager, _proxyUri.IdnHost);
 0271                _sslOptionsProxy.ApplicationProtocols = null;
 0272            }
 273
 0274            if (NetEventSource.Log.IsEnabled()) Trace($"{this}");
 0275        }
 276
 277        private static SslClientAuthenticationOptions ConstructSslOptions(HttpConnectionPoolManager poolManager, string 
 0278        {
 0279            Debug.Assert(sslHostName != null);
 280
 0281            SslClientAuthenticationOptions sslOptions = poolManager.Settings._sslOptions?.ShallowClone() ?? new SslClien
 282
 283            // This is only set if we are underlying handler for HttpClientHandler
 0284            if (poolManager.Settings._clientCertificateOptions == ClientCertificateOption.Manual && sslOptions.LocalCert
 0285                    (sslOptions.ClientCertificates == null || sslOptions.ClientCertificates.Count == 0))
 0286            {
 287                // If we have no client certificates do not set callback when internal selection is used.
 288                // It breaks TLS resume on Linux
 0289                sslOptions.LocalCertificateSelectionCallback = null;
 0290            }
 291
 292            // Set TargetHost for SNI
 0293            sslOptions.TargetHost = sslHostName;
 294
 0295            return sslOptions;
 0296        }
 297
 0298        public string? TelemetryServerAddress => _telemetryServerAddress;
 0299        public HttpAuthority OriginAuthority => _originAuthority;
 0300        public HttpConnectionSettings Settings => _poolManager.Settings;
 0301        public HttpConnectionKind Kind => _kind;
 0302        public bool IsSecure => _kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel || _kind
 0303        public Uri? ProxyUri => _proxyUri;
 0304        public ICredentials? ProxyCredentials => _poolManager.ProxyCredentials;
 0305        public PreAuthCredentialCache? PreAuthCredentials => _preAuthCredentials;
 0306        public bool IsDefaultPort => OriginAuthority.Port == (IsSecure ? DefaultHttpsPort : DefaultHttpPort);
 0307        private bool DoProxyAuth => (_kind == HttpConnectionKind.Proxy || _kind == HttpConnectionKind.ProxyConnect);
 308
 309        /// <summary>Object used to synchronize access to state in the pool.</summary>
 310        private object SyncObj
 311        {
 312            get
 0313            {
 0314                Debug.Assert(!Monitor.IsEntered(_http11Connections));
 0315                return _http11Connections;
 0316            }
 317        }
 318
 0319        public bool HasSyncObjLock => Monitor.IsEntered(_http11Connections);
 320
 321        // Overview of connection management (mostly HTTP version independent):
 322        //
 323        // Each version of HTTP (1.1, 2, 3) has its own connection pool, and each of these work in a similar manner,
 324        // allowing for differences between the versions (most notably, HTTP/1.1 is not multiplexed.)
 325        //
 326        // When a request is submitted for a particular version (e.g. HTTP/1.1), we first look in the pool for available
 327        // An "available" connection is one that is (hopefully) usable for a new request.
 328        //      For HTTP/1.1, this is just an idle connection.
 329        //      For HTTP2/3, this is a connection that (hopefully) has available streams to use for new requests.
 330        // If we find an available connection, we will attempt to validate it and then use it.
 331        //      We check the lifetime of the connection and discard it if the lifetime is exceeded.
 332        //      We check that the connection has not shut down; if so we discard it.
 333        //      For HTTP2/3, we reserve a stream on the connection. If this fails, we cannot use the connection right no
 334        // If validation fails, we will attempt to find a different available connection.
 335        //
 336        // Once we have found a usable connection, we use it to process the request.
 337        //      For HTTP/1.1, a connection can handle only a single request at a time, thus it is immediately removed fr
 338        //      For HTTP2/3, a connection is only removed from the available list when it has no more available streams.
 339        //      In either case, the connection still counts against the total associated connection count for the pool.
 340        //
 341        // If we cannot find a usable available connection, then the request is added the to the request queue for the a
 342        //
 343        // Whenever a request is queued, or an existing connection shuts down, we will check to see if we should inject 
 344        // Injection policy depends on both user settings and some simple heuristics.
 345        // See comments on the relevant routines for details on connection injection policy.
 346        //
 347        // When a new connection is successfully created, or an existing unavailable connection becomes available again,
 348        // we will attempt to use this connection to handle any queued requests (subject to lifetime restrictions on exi
 349        // This may result in the connection becoming unavailable again, because it cannot handle any more requests at t
 350        // If not, we will return the connection to the pool as an available connection for use by new requests.
 351        //
 352        // When a connection shuts down, either gracefully (e.g. GOAWAY) or abortively (e.g. IOException),
 353        // we will remove it from the list of available connections, if it is present there.
 354        // If not, then it must be unavailable at the moment; we will detect this and ensure it is not added back to the
 355
 356        public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, Canc
 0357        {
 358            // We need the User-Agent header when we send a CONNECT request to the proxy.
 359            // We must read the header early, before we return the ownership of the request back to the user.
 0360            if ((Kind is HttpConnectionKind.ProxyTunnel or HttpConnectionKind.SslProxyTunnel) &&
 0361                request.HasHeaders &&
 0362                request.Headers.NonValidated.TryGetValues(HttpKnownHeaderNames.UserAgent, out HeaderStringValues userAge
 0363            {
 0364                _connectTunnelUserAgent = userAgent.ToString();
 0365            }
 366
 0367            if (doRequestAuth && Settings._credentials != null)
 0368            {
 0369                return AuthenticationHelper.SendWithRequestAuthAsync(request, async, Settings._credentials, Settings._pr
 370            }
 371
 0372            return SendWithProxyAuthAsync(request, async, doRequestAuth, cancellationToken);
 0373        }
 374
 375        public ValueTask<HttpResponseMessage> SendWithProxyAuthAsync(HttpRequestMessage request, bool async, bool doRequ
 0376        {
 0377            if (DoProxyAuth && ProxyCredentials is not null)
 0378            {
 0379                return AuthenticationHelper.SendWithProxyAuthAsync(request, _proxyUri!, async, ProxyCredentials, doReque
 380            }
 381
 0382            return SendWithVersionDetectionAndRetryAsync(request, async, doRequestAuth, cancellationToken);
 0383        }
 384
 385        private Task<HttpResponseMessage> SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage re
 0386        {
 0387            if (doRequestAuth && Settings._credentials != null)
 0388            {
 0389                return AuthenticationHelper.SendWithNtConnectionAuthAsync(request, async, Settings._credentials, Setting
 390            }
 391
 0392            return SendWithNtProxyAuthAsync(connection, request, async, cancellationToken);
 0393        }
 394
 395        public Task<HttpResponseMessage> SendWithNtProxyAuthAsync(HttpConnection connection, HttpRequestMessage request,
 0396        {
 0397            if (DoProxyAuth && ProxyCredentials is not null)
 0398            {
 0399                return AuthenticationHelper.SendWithNtProxyAuthAsync(request, ProxyUri!, async, ProxyCredentials, HttpHa
 400            }
 401
 0402            return connection.SendAsync(request, async, cancellationToken);
 0403        }
 404
 405        public async ValueTask<HttpResponseMessage> SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, bo
 0406        {
 0407            _usedSinceLastCleanup = true;
 408
 409            // Loop on connection failures (or other problems like version downgrade) and retry if possible.
 0410            int retryCount = 0;
 0411            while (true)
 0412            {
 413                // Reset any connection id stamped by a previous attempt. Each connection sets it again in its
 414                // SendAsync, so if this attempt is abandoned (e.g. we time out while waiting for the next
 415                // connection after a graceful retry) the request won't point at a connection that didn't serve it.
 0416                request.ConnectionId = null;
 417
 0418                HttpConnectionWaiter<HttpConnection>? http11ConnectionWaiter = null;
 0419                HttpConnectionWaiter<Http2Connection?>? http2ConnectionWaiter = null;
 420                try
 0421                {
 0422                    HttpResponseMessage? response = null;
 423
 424                    // Use HTTP/3 if possible.
 0425                    if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3 && // guard to enable trimming HTTP/3 support
 0426                        _http3Enabled &&
 0427                        (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHighe
 0428                        !request.IsExtendedConnectRequest)
 0429                    {
 0430                        Debug.Assert(async);
 0431                        if (QuicConnection.IsSupported)
 0432                        {
 0433                            if (_sslOptionsHttp3 == null)
 0434                            {
 435                                // deferred creation. We use atomic exchange to be sure all threads point to single obje
 0436                                SslClientAuthenticationOptions sslOptionsHttp3 = ConstructSslOptions(_poolManager, _sslO
 0437                                sslOptionsHttp3.ApplicationProtocols = s_http3ApplicationProtocols;
 0438                                Interlocked.CompareExchange(ref _sslOptionsHttp3, sslOptionsHttp3, null);
 0439                            }
 440
 0441                            response = await TrySendUsingHttp3Async(request, cancellationToken).ConfigureAwait(false);
 0442                        }
 443                        else
 0444                        {
 0445                            _altSvcEnabled = false;
 0446                            _http3Enabled = false;
 0447                        }
 0448                    }
 449
 0450                    if (response is null)
 0451                    {
 452                        // We could not use HTTP/3. Do not continue if downgrade is not allowed.
 0453                        if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLow
 0454                        {
 0455                            ThrowGetVersionException(request, 3);
 456                        }
 457
 458                        // Use HTTP/2 if possible.
 0459                        if (_http2Enabled &&
 0460                            (request.Version.Major >= 2 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrH
 0461                            (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower || IsSecure) && // prefer 
 0462                            !(_http2SessionAuthSeen && CanFallBackToHttp11(request))) // skip HTTP/2 for requests that c
 0463                        {
 0464                            if (!TryGetPooledHttp2Connection(request, out Http2Connection? connection, out http2Connecti
 0465                                http2ConnectionWaiter != null)
 0466                            {
 0467                                connection = await http2ConnectionWaiter.WaitForConnectionAsync(request, this, async, ca
 0468                            }
 469
 0470                            Debug.Assert(connection is not null || !_http2Enabled);
 0471                            if (connection is not null)
 0472                            {
 0473                                if (request.IsExtendedConnectRequest)
 0474                                {
 0475                                    await connection.InitialSettingsReceived.WaitWithCancellationAsync(cancellationToken
 0476                                    if (!connection.IsConnectEnabled)
 0477                                    {
 0478                                        HttpRequestException exception = new(HttpRequestError.ExtendedConnectNotSupporte
 0479                                        exception.Data["SETTINGS_ENABLE_CONNECT_PROTOCOL"] = false;
 0480                                        throw exception;
 481                                    }
 0482                                }
 483
 0484                                response = await connection.SendAsync(request, async, cancellationToken).ConfigureAwait(
 0485                            }
 0486                        }
 487
 0488                        if (response is null)
 0489                        {
 490                            // We could not use HTTP/2. Do not continue if downgrade is not allowed.
 0491                            if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionO
 0492                            {
 0493                                ThrowGetVersionException(request, 2);
 494                            }
 495
 496                            // Use HTTP/1.x.
 0497                            if (!TryGetPooledHttp11Connection(request, async, out HttpConnection? connection, out http11
 0498                            {
 0499                                connection = await http11ConnectionWaiter.WaitForConnectionAsync(request, this, async, c
 0500                            }
 501
 0502                            connection.Acquire(); // In case we are doing Windows (i.e. connection-based) auth, we need 
 503                            try
 0504                            {
 0505                                response = await SendWithNtConnectionAuthAsync(connection, request, async, doRequestAuth
 0506                            }
 507                            finally
 0508                            {
 0509                                connection.Release();
 0510                            }
 0511                        }
 0512                    }
 513
 0514                    ProcessAltSvc(response);
 0515                    return response;
 516                }
 0517                catch (HttpRequestException e) when (e.AllowRetry == RequestRetryType.RetryOnConnectionFailure)
 0518                {
 0519                    Debug.Assert(retryCount >= 0 && retryCount <= MaxConnectionFailureRetries);
 520
 0521                    if (retryCount == MaxConnectionFailureRetries)
 0522                    {
 0523                        if (NetEventSource.Log.IsEnabled())
 0524                        {
 0525                            Trace($"MaxConnectionFailureRetries limit of {MaxConnectionFailureRetries} hit. Retryable re
 0526                        }
 527
 0528                        throw;
 529                    }
 530
 0531                    retryCount++;
 532
 0533                    if (NetEventSource.Log.IsEnabled())
 0534                    {
 0535                        Trace($"Retry attempt {retryCount} after connection failure. Connection exception: {e}");
 0536                    }
 537
 538                    // Eat exception and try again.
 0539                }
 0540                catch (HttpRequestException e) when (e.AllowRetry == RequestRetryType.RetryOnLowerHttpVersion)
 541                {
 542                    // Throw if fallback is not allowed by the version policy.
 543                    if (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
 544                    {
 545                        throw new HttpRequestException(HttpRequestError.VersionNegotiationError, SR.Format(SR.net_http_r
 546                    }
 547
 548                    if (NetEventSource.Log.IsEnabled())
 549                    {
 550                        Trace($"Retrying request because server requested version fallback: {e}");
 551                    }
 552
 553                    // Eat exception and try again on a lower protocol version.
 554                    request.Version = HttpVersion.Version11;
 555                }
 0556                catch (HttpRequestException e) when (e.AllowRetry == RequestRetryType.RetryOnSessionAuthenticationChalle
 557                {
 558                    // Server sent a session-based authentication challenge (Negotiate/NTLM) on HTTP/2.
 559                    // These authentication schemes require a persistent connection and don't work properly over HTTP/2.
 560                    // The pool flag was already set in Http2Connection.SendAsync so future requests that can use
 561                    // HTTP/1.1 will go directly to HTTP/1.1. Retry this request on HTTP/1.1.
 562                    Debug.Assert(CanFallBackToHttp11(request));
 563                    Debug.Assert(_http2SessionAuthSeen);
 564
 565                    request.Version = HttpVersion.Version11;
 566                }
 567                finally
 0568                {
 569                    // We never cancel both attempts at the same time. When downgrade happens, it's possible that both w
 570                    // but in that case http2ConnectionWaiter.ConnectionCancellationTokenSource shall be null.
 0571                    Debug.Assert(http11ConnectionWaiter is null || http2ConnectionWaiter?.ConnectionCancellationTokenSou
 0572                    http11ConnectionWaiter?.SetTimeoutToPendingConnectionAttempt(this, cancellationToken.IsCancellationR
 0573                    http2ConnectionWaiter?.SetTimeoutToPendingConnectionAttempt(this, cancellationToken.IsCancellationRe
 0574                }
 0575            }
 0576        }
 577
 578        private async ValueTask<(Stream, TransportContext?, Activity?, IPEndPoint?, long)> ConnectAsync(HttpRequestMessa
 0579        {
 0580            Stream? stream = null;
 0581            IPEndPoint? remoteEndPoint = null;
 0582            Exception? exception = null;
 0583            TransportContext? transportContext = null;
 584
 585            // Allocate the connection id up front so it can be surfaced to a custom ConnectCallback (via
 586            // SocketsHttpConnectionContext) and reused as the final connection's Id, allowing the caller to
 587            // correlate connect-time state with the connection (e.g. in the ShouldEvictConnection callback).
 0588            long connectionId = HttpConnectionBase.GetNextConnectionId();
 589
 0590            Activity? activity = ConnectionSetupDistributedTracing.StartConnectionSetupActivity(IsSecure, _telemetryServ
 591
 592            try
 0593            {
 0594                switch (_kind)
 595                {
 596                    case HttpConnectionKind.Http:
 597                    case HttpConnectionKind.Https:
 598                    case HttpConnectionKind.ProxyConnect:
 0599                        stream = await ConnectToTcpHostAsync(_originAuthority.IdnHost, _originAuthority.Port, request, a
 600                        // remoteEndPoint is returned for diagnostic purposes.
 0601                        remoteEndPoint = GetRemoteEndPoint(stream);
 0602                        if (_kind == HttpConnectionKind.ProxyConnect && _sslOptionsProxy != null)
 0603                        {
 0604                            stream = await ConnectHelper.EstablishSslConnectionAsync(_sslOptionsProxy, request, async, s
 0605                        }
 0606                        break;
 607
 608                    case HttpConnectionKind.Proxy:
 0609                        stream = await ConnectToTcpHostAsync(_proxyUri!.IdnHost, _proxyUri.Port, request, async, connect
 610                        // remoteEndPoint is returned for diagnostic purposes.
 0611                        remoteEndPoint = GetRemoteEndPoint(stream);
 0612                        if (_sslOptionsProxy != null)
 0613                        {
 0614                            stream = await ConnectHelper.EstablishSslConnectionAsync(_sslOptionsProxy, request, async, s
 0615                        }
 0616                        break;
 617
 618                    case HttpConnectionKind.ProxyTunnel:
 619                    case HttpConnectionKind.SslProxyTunnel:
 0620                        stream = await EstablishProxyTunnelAsync(async, cancellationToken).ConfigureAwait(false);
 621
 0622                        if (stream is HttpContentStream contentStream && contentStream._connection?._stream is Stream in
 0623                        {
 0624                            remoteEndPoint = GetRemoteEndPoint(innerStream);
 0625                        }
 626
 0627                        break;
 628
 629                    case HttpConnectionKind.SocksTunnel:
 630                    case HttpConnectionKind.SslSocksTunnel:
 0631                        stream = await EstablishSocksTunnel(request, async, connectionId, cancellationToken).ConfigureAw
 632                        // remoteEndPoint is returned for diagnostic purposes.
 0633                        remoteEndPoint = GetRemoteEndPoint(stream);
 0634                        break;
 635                }
 636
 0637                Debug.Assert(stream != null);
 638
 0639                if (IsSecure)
 0640                {
 0641                    SslStream? sslStream = stream as SslStream;
 0642                    if (sslStream == null)
 0643                    {
 0644                        sslStream = await ConnectHelper.EstablishSslConnectionAsync(GetSslOptionsForRequest(request, isF
 0645                    }
 646                    else
 0647                    {
 0648                        if (NetEventSource.Log.IsEnabled())
 0649                        {
 0650                            Trace($"Connected with custom SslStream: alpn='${sslStream.NegotiatedApplicationProtocol}'")
 0651                        }
 0652                    }
 0653                    transportContext = sslStream.TransportContext;
 0654                    stream = sslStream;
 0655                }
 0656            }
 0657            catch (Exception ex) when (activity is not null)
 0658            {
 0659                exception = ex;
 0660                throw;
 661            }
 662            finally
 0663            {
 0664                if (activity is not null)
 0665                {
 0666                    ConnectionSetupDistributedTracing.StopConnectionSetupActivity(activity, exception, remoteEndPoint);
 0667                }
 0668            }
 669
 0670            return (stream, transportContext, activity, remoteEndPoint, connectionId);
 671
 0672            static IPEndPoint? GetRemoteEndPoint(Stream stream) => (stream as NetworkStream)?.Socket?.RemoteEndPoint as 
 0673        }
 674
 675        private async ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, HttpRequestMessage initialRequest, 
 0676        {
 0677            cancellationToken.ThrowIfCancellationRequested();
 678
 0679            var endPoint = new DnsEndPoint(host, port);
 0680            Stream? stream = null;
 681            try
 0682            {
 683                // If a ConnectCallback was supplied, use that to establish the connection.
 0684                if (Settings._connectCallback != null)
 0685                {
 0686                    ValueTask<Stream> streamTask = Settings._connectCallback(new SocketsHttpConnectionContext(endPoint, 
 687
 0688                    if (!async && !streamTask.IsCompleted)
 0689                    {
 690                        // User-provided ConnectCallback is completing asynchronously but the user is making a synchrono
 691                        // set it up so that synchronous requests are made on a handler with a synchronously-completing 
 692                        // we could add a Boolean to SocketsHttpConnectionContext (https://github.com/dotnet/runtime/iss
 693                        // this request is sync or async.
 0694                        Trace($"{nameof(SocketsHttpHandler.ConnectCallback)} completing asynchronously for a synchronous
 0695                    }
 696
 0697                    stream = await streamTask.ConfigureAwait(false) ?? throw new HttpRequestException(SR.net_http_null_f
 0698                }
 699                else
 0700                {
 701                    // Otherwise, create and connect a socket using default settings.
 0702                    Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
 703                    try
 0704                    {
 0705                        if (async)
 0706                        {
 0707                            await socket.ConnectAsync(endPoint, cancellationToken).ConfigureAwait(false);
 0708                        }
 709                        else
 0710                        {
 0711                            using (cancellationToken.UnsafeRegister(static s => ((Socket)s!).Dispose(), socket))
 0712                            {
 0713                                socket.Connect(endPoint);
 0714                            }
 0715                        }
 716
 0717                        stream = new NetworkStream(socket, ownsSocket: true);
 0718                    }
 0719                    catch
 0720                    {
 0721                        socket.Dispose();
 0722                        throw;
 723                    }
 0724                }
 725
 0726                return stream;
 727            }
 0728            catch (Exception ex)
 0729            {
 0730                throw ex is OperationCanceledException oce && oce.CancellationToken == cancellationToken ?
 0731                    CancellationHelper.CreateOperationCanceledException(innerException: null, cancellationToken) :
 0732                    ConnectHelper.CreateWrappedException(ex, host, port, cancellationToken);
 733            }
 0734        }
 735
 736        private SslClientAuthenticationOptions GetSslOptionsForRequest(HttpRequestMessage request, bool isForHttp2)
 0737        {
 738            // Even if a request could use HTTP/2, we may have chosen to establish an HTTP/1.1 connection
 739            // for it instead (e.g. when _http2SessionAuthSeen is set for downgradeable requests).
 0740            if (_http2Enabled && isForHttp2)
 0741            {
 0742                if (request.Version.Major >= 2 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
 0743                {
 0744                    return _sslOptionsHttp2Only!;
 745                }
 746
 0747                if (request.Version.Major >= 2 || request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher)
 0748                {
 0749                    return _sslOptionsHttp2!;
 750                }
 0751            }
 0752            return _sslOptionsHttp11!;
 0753        }
 754
 755        private async ValueTask<Stream> ApplyPlaintextFilterAsync(bool async, Stream stream, Version httpVersion, HttpRe
 0756        {
 0757            if (Settings._plaintextStreamFilter is null)
 0758            {
 0759                return stream;
 760            }
 761
 762            Stream newStream;
 763            try
 0764            {
 0765                ValueTask<Stream> streamTask = Settings._plaintextStreamFilter(new SocketsHttpPlaintextStreamFilterConte
 766
 0767                if (!async && !streamTask.IsCompleted)
 0768                {
 769                    // User-provided PlaintextStreamFilter is completing asynchronously but the user is making a synchro
 770                    // set it up so that synchronous requests are made on a handler with a synchronously-completing Plai
 771                    // we could add a Boolean to SocketsHttpPlaintextStreamFilterContext (https://github.com/dotnet/runt
 772                    // this request is sync or async.
 0773                    Trace($"{nameof(SocketsHttpHandler.PlaintextStreamFilter)} completing asynchronously for a synchrono
 0774                }
 775
 0776                newStream = await streamTask.ConfigureAwait(false);
 0777            }
 0778            catch (OperationCanceledException oce) when (oce.CancellationToken == cancellationToken)
 0779            {
 0780                stream.Dispose();
 0781                throw;
 782            }
 0783            catch (Exception e)
 0784            {
 0785                stream.Dispose();
 0786                throw new HttpRequestException(SR.net_http_exception_during_plaintext_filter, e);
 787            }
 788
 0789            if (newStream == null)
 0790            {
 0791                stream.Dispose();
 0792                throw new HttpRequestException(SR.net_http_null_from_plaintext_filter);
 793            }
 794
 0795            return newStream;
 0796        }
 797
 798        private async ValueTask<Stream> EstablishProxyTunnelAsync(bool async, CancellationToken cancellationToken)
 0799        {
 800            // Send a CONNECT request to the proxy server to establish a tunnel.
 0801            HttpRequestMessage tunnelRequest = new HttpRequestMessage(HttpMethod.Connect, _proxyUri);
 0802            tunnelRequest.Headers.Host = $"{_originAuthority.IdnHost}:{_originAuthority.Port}";    // This specifies des
 803
 0804            if (_connectTunnelUserAgent is not null)
 0805            {
 0806                tunnelRequest.Headers.TryAddWithoutValidation(KnownHeaders.UserAgent.Descriptor, _connectTunnelUserAgent
 0807            }
 808
 0809            HttpResponseMessage tunnelResponse = await _poolManager.SendProxyConnectAsync(tunnelRequest, _proxyUri!, asy
 810
 0811            if (!tunnelResponse.IsSuccessStatusCode)
 0812            {
 0813                tunnelResponse.Dispose();
 0814                throw new HttpRequestException(HttpRequestError.ProxyTunnelError, SR.Format(SR.net_http_proxy_tunnel_ret
 815            }
 816
 817            try
 0818            {
 0819                return tunnelResponse.Content.ReadAsStream(cancellationToken);
 820            }
 0821            catch
 0822            {
 0823                tunnelResponse.Dispose();
 0824                throw;
 825            }
 0826        }
 827
 828        private async ValueTask<Stream> EstablishSocksTunnel(HttpRequestMessage request, bool async, long connectionId, 
 0829        {
 0830            Debug.Assert(_proxyUri != null);
 831
 0832            Stream stream = await ConnectToTcpHostAsync(_proxyUri.IdnHost, _proxyUri.Port, request, async, connectionId,
 833
 834            try
 0835            {
 0836                await SocksHelper.EstablishSocksTunnelAsync(stream, _originAuthority.IdnHost, _originAuthority.Port, _pr
 0837            }
 0838            catch (Exception e) when (e is not OperationCanceledException)
 0839            {
 0840                Debug.Assert(e is not HttpRequestException);
 0841                throw new HttpRequestException(HttpRequestError.ProxyTunnelError, SR.net_http_proxy_tunnel_error, e);
 842            }
 843
 0844            return stream;
 0845        }
 846
 847        private CancellationTokenSource GetConnectTimeoutCancellationTokenSource<T>(HttpConnectionWaiter<T> waiter)
 848            where T : HttpConnectionBase?
 0849        {
 0850            var cts = new CancellationTokenSource(Settings._connectTimeout);
 851
 0852            lock (waiter)
 0853            {
 854                // After a request completes (or is canceled), it will call into SetTimeoutToPendingConnectionAttempt,
 855                // which will no-op if ConnectionCancellationTokenSource is not set, assuming that the connection attemp
 856                // As the initiating request for this connection attempt may complete concurrently at any time,
 857                // there is a race condition where the first call to SetTimeoutToPendingConnectionAttempt may happen
 858                // before we were able to set the CTS, so no timeout will be applied even though the request is already 
 0859                waiter.ConnectionCancellationTokenSource = cts;
 860
 861                // To fix that, we check whether the waiter already completed now that we're holding a lock.
 862                // If it had, call SetTimeoutToPendingConnectionAttempt again now that the CTS is set.
 0863                if (waiter.Task.IsCompleted)
 0864                {
 0865                    waiter.SetTimeoutToPendingConnectionAttempt(this, requestCancelled: waiter.Task.IsCanceled);
 0866                    waiter.ConnectionCancellationTokenSource = null;
 0867                }
 0868            }
 869
 0870            return cts;
 0871        }
 872
 873        private static Exception CreateConnectTimeoutException(OperationCanceledException oce)
 0874        {
 875            // The pattern for request timeouts (on HttpClient) is to throw an OCE with an inner exception of TimeoutExc
 876            // Do the same for ConnectTimeout-based timeouts.
 0877            TimeoutException te = new TimeoutException(SR.net_http_connect_timedout, oce.InnerException);
 0878            Exception newException = CancellationHelper.CreateOperationCanceledException(te, oce.CancellationToken);
 0879            ExceptionDispatchInfo.SetCurrentStackTrace(newException);
 0880            return newException;
 0881        }
 882
 883        [DoesNotReturn]
 884        private static void ThrowGetVersionException(HttpRequestMessage request, int desiredVersion, Exception? inner = 
 0885        {
 0886            Debug.Assert(desiredVersion == 2 || desiredVersion == 3);
 887
 0888            HttpRequestException ex = new(HttpRequestError.VersionNegotiationError, SR.Format(SR.net_http_requested_vers
 0889            if (request.IsExtendedConnectRequest && desiredVersion == 2)
 0890            {
 0891                ex.Data["HTTP2_ENABLED"] = false;
 0892            }
 893
 0894            throw ex;
 895        }
 896
 897        /// <summary>
 898        /// Determines whether a request that was sent over HTTP/2 can fall back to HTTP/1.1.
 899        /// This matches the version negotiation logic: a request can use HTTP/1.1 if its
 900        /// <see cref="HttpRequestMessage.Version"/> is less than 2.0 or if its
 901        /// <see cref="HttpRequestMessage.VersionPolicy"/> is <see cref="HttpVersionPolicy.RequestVersionOrLower"/>.
 902        /// </summary>
 903        internal static bool CanFallBackToHttp11(HttpRequestMessage request) =>
 0904            request.Version.Major < 2 || request.VersionPolicy == HttpVersionPolicy.RequestVersionOrLower;
 905
 906        private bool CheckExpirationOnGet(HttpConnectionBase connection)
 0907        {
 0908            Debug.Assert(!HasSyncObjLock);
 909
 0910            if (connection.MarkedForEviction)
 0911            {
 0912                return true;
 913            }
 914
 0915            TimeSpan pooledConnectionLifetime = _poolManager.Settings._pooledConnectionLifetime;
 0916            if (pooledConnectionLifetime != Timeout.InfiniteTimeSpan)
 0917            {
 0918                return connection.Age > pooledConnectionLifetime;
 919            }
 920
 0921            return false;
 0922        }
 923
 924        private bool CheckExpirationOnReturn(HttpConnectionBase connection)
 0925        {
 0926            if (connection.MarkedForEviction)
 0927            {
 0928                return true;
 929            }
 930
 0931            TimeSpan lifetime = _poolManager.Settings._pooledConnectionLifetime;
 0932            if (lifetime != Timeout.InfiniteTimeSpan)
 0933            {
 0934                return lifetime == TimeSpan.Zero || connection.Age > lifetime;
 935            }
 936
 0937            return false;
 0938        }
 939
 940        /// <summary>
 941        /// Incremented at the start of each eviction evaluation pass. Connections record the generation at which they
 942        /// were last evaluated, so an HTTP/1.1 connection that was busy during a pass (and therefore not visible to it)
 943        /// can be re-evaluated in the background when it is returned to the pool.
 944        /// </summary>
 0945        internal int EvictionGeneration { get; private set; }
 946
 947        /// <summary>
 948        /// Invokes the user-supplied <see cref="SocketsHttpHandler.ShouldEvictConnection"/> callback for each
 949        /// pooled connection and marks for eviction those the callback selects. List snapshots are taken under
 950        /// the pool lock; the per-connection checks are started outside the lock and intentionally not awaited.
 951        /// </summary>
 952        private void EvaluateConnectionsForEviction()
 0953        {
 0954            Debug.Assert(!HasSyncObjLock);
 955
 0956            EvictionGeneration++;
 957
 958            try
 0959            {
 960                // Each connection's eviction check is started but deliberately not awaited. The user callback may
 961                // block or take a long time, and awaiting the checks one by one would let a single slow callback
 962                // stall the evaluation of every other connection (and every subsequent eviction pass). Each
 963                // connection guards against overlapping runs of its own callback, so a connection whose callback is
 964                // still pending is simply skipped by later passes. A callback that completes synchronously still
 965                // completes inline here, so this only changes behavior when a callback does not complete promptly.
 966
 967                // HTTP/1.1: the idle stack is lock-free and its enumerator returns a snapshot, so we can inspect
 968                // connections without removing them. Connections currently in use (and therefore not on the stack)
 969                // are evaluated by ReturnHttp11Connection when they are returned to the pool.
 0970                foreach (HttpConnection connection in _http11Connections)
 0971                {
 0972                    _ = connection.EvaluateForEvictionAsync();
 0973                }
 974
 975                // HTTP/2: Get the list of available connections under the lock, then evaluate outside of it.
 0976                ReadOnlySpan<Http2Connection> http2Connections = default;
 0977                lock (SyncObj)
 0978                {
 0979                    if (_availableHttp2Connections is { Count: > 0 } http2)
 0980                    {
 0981                        http2Connections = CollectionsMarshal.AsSpan(http2);
 0982                    }
 0983                }
 984
 0985                foreach (Http2Connection connection in http2Connections)
 0986                {
 987                    // The span may be modified concurrently, so check for null connections
 0988                    _ = connection?.EvaluateForEvictionAsync();
 0989                }
 990
 0991                if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3)
 0992                {
 0993                    ReadOnlySpan<Http3Connection> http3Connections = default;
 0994                    lock (SyncObj)
 0995                    {
 0996                        if (_availableHttp3Connections is { Count: > 0 } http3)
 0997                        {
 0998                            http3Connections = CollectionsMarshal.AsSpan(http3);
 0999                        }
 01000                    }
 1001
 01002                    foreach (Http3Connection connection in http3Connections)
 01003                    {
 1004                        // The span may be modified concurrently, so check for null connections
 01005                        _ = connection?.EvaluateForEvictionAsync();
 01006                    }
 01007                }
 01008            }
 01009            catch (Exception e)
 01010            {
 01011                Debug.Fail($"Unexpected exception while evaluating connections for eviction: {e}");
 1012
 1013                if (NetEventSource.Log.IsEnabled()) Trace($"Unexpected exception while evaluating connections for evicti
 1014            }
 01015        }
 1016
 1017        /// <summary>
 1018        /// Disposes the connection pool.  This is only needed when the pool currently contains
 1019        /// or has associated connections.
 1020        /// </summary>
 1021        public void Dispose()
 01022        {
 01023            List<HttpConnectionBase>? toDispose = null;
 1024
 01025            lock (SyncObj)
 01026            {
 01027                if (_disposed)
 01028                {
 01029                    return;
 1030                }
 1031
 01032                _disposed = true;
 01033                _http11RequestQueueIsEmptyAndNotDisposed = false;
 1034
 01035                if (NetEventSource.Log.IsEnabled()) Trace("Disposing the pool.");
 1036
 01037                if (_availableHttp2Connections is not null)
 01038                {
 01039                    toDispose = [.. _availableHttp2Connections];
 01040                    _associatedHttp2ConnectionCount -= _availableHttp2Connections.Count;
 01041                    _availableHttp2Connections.Clear();
 01042                }
 1043
 01044                if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3 && _availableHttp3Connections is not null)
 01045                {
 01046                    toDispose ??= new();
 01047                    toDispose.AddRange(_availableHttp3Connections);
 01048                    _associatedHttp3ConnectionCount -= _availableHttp3Connections.Count;
 01049                    _availableHttp3Connections.Clear();
 01050                }
 1051
 01052                if (_authorityExpireTimer != null)
 01053                {
 01054                    _authorityExpireTimer.Dispose();
 01055                    _authorityExpireTimer = null;
 01056                }
 1057
 01058                if (_altSvcBlocklistTimerCancellation != null)
 01059                {
 01060                    _altSvcBlocklistTimerCancellation.Cancel();
 01061                    _altSvcBlocklistTimerCancellation.Dispose();
 01062                    _altSvcBlocklistTimerCancellation = null;
 01063                }
 1064
 01065                Debug.Assert((_availableHttp2Connections?.Count ?? 0) == 0, $"Expected {nameof(_availableHttp2Connection
 01066            }
 1067
 1068            // Dispose connections outside the lock to avoid lock re-entrancy issues.
 1069
 1070            // This will trigger the disposal of Http11 connections.
 1071            // Note: Http11 connections will decrement the _associatedHttp11ConnectionCount when disposed.
 1072            // Http2 connections will not, hence the difference in handing _associatedHttp2ConnectionCount.
 01073            ProcessHttp11RequestQueue(null);
 1074
 01075            toDispose?.ForEach(c => c.Dispose());
 01076        }
 1077
 1078        /// <summary>
 1079        /// Removes any unusable connections from the pool, and if the pool
 1080        /// is then empty and stale, disposes of it.
 1081        /// </summary>
 1082        /// <returns>
 1083        /// true if the pool disposes of itself; otherwise, false.
 1084        /// </returns>
 1085        public bool CleanCacheAndDisposeIfUnused()
 01086        {
 01087            TimeSpan pooledConnectionLifetime = _poolManager.Settings._pooledConnectionLifetime;
 01088            TimeSpan pooledConnectionIdleTimeout = _poolManager.Settings._pooledConnectionIdleTimeout;
 01089            long nowTicks = Environment.TickCount64;
 1090
 1091            // If the user supplied an eviction callback, give them a chance to mark pooled connections for
 1092            // eviction before we scavenge. The callback is asynchronous and may be slow (e.g. perform a DNS
 1093            // lookup), so it runs off the maintenance timer thread; connections it evicts are retired by a later
 1094            // scavenge pass or by the get/return paths.
 01095            if (_poolManager.Settings._shouldEvictConnection is not null)
 01096            {
 01097                EvaluateConnectionsForEviction();
 01098            }
 1099
 01100            List<HttpConnectionBase>? toDispose = null;
 1101
 01102            lock (SyncObj)
 01103            {
 1104                // If there are now no connections associated with this pool, we can dispose of it. We
 1105                // avoid aggressively cleaning up pools that have recently been used but currently aren't;
 1106                // if a pool was used since the last time we cleaned up, give it another chance. New pools
 1107                // start out saying they've recently been used, to give them a bit of breathing room and time
 1108                // for the initial collection to be added to it.
 01109                if (!_usedSinceLastCleanup && _associatedHttp11ConnectionCount == 0 && _associatedHttp2ConnectionCount =
 01110                    // An HTTP/2 connection may still be draining requests (e.g. after a GOAWAY frame) and need heart be
 01111                    (_http2ConnectionsForHeartBeat?.Count ?? 0) == 0)
 01112                {
 01113                    _disposed = true;
 01114                    return true; // Pool is disposed of.  It should be removed.
 1115                }
 1116
 1117                // Reset the cleanup flag.  Any pools that are empty and not used since the last cleanup
 1118                // will be purged next time around.
 01119                _usedSinceLastCleanup = false;
 1120
 01121                ScavengeHttp11ConnectionStack(this, _http11Connections, ref toDispose, nowTicks, pooledConnectionLifetim
 1122
 01123                if (_availableHttp2Connections is not null)
 01124                {
 01125                    int removed = ScavengeHttp2ConnectionList(_availableHttp2Connections, ref toDispose, nowTicks, poole
 01126                    _associatedHttp2ConnectionCount -= removed;
 1127
 1128                    // Note: Http11 connections will decrement the _associatedHttp11ConnectionCount when disposed.
 1129                    // Http2 connections will not, hence the difference in handing _associatedHttp2ConnectionCount.
 01130                }
 01131                if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3 && _availableHttp3Connections is not null)
 01132                {
 01133                    int removed = ScavengeHttp3ConnectionList(_availableHttp3Connections, ref toDispose, nowTicks, poole
 01134                    _associatedHttp3ConnectionCount -= removed;
 1135
 1136                    // Note: Http11 connections will decrement the _associatedHttp11ConnectionCount when disposed.
 1137                    // Http3 connections will not, hence the difference in handing _associatedHttp3ConnectionCount.
 01138                }
 01139            }
 1140
 1141            // Dispose the stale connections outside the pool lock, to avoid holding the lock too long.
 1142            // Dispose them asynchronously to not to block the caller on closing the SslStream or NetworkStream.
 01143            if (toDispose is not null)
 01144            {
 01145                Task.Factory.StartNew(static s => ((List<HttpConnectionBase>)s!).ForEach(c => c.Dispose()), toDispose,
 01146                    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 01147            }
 1148
 1149            // Pool is active.  Should not be removed.
 01150            return false;
 01151        }
 1152
 1153        // For diagnostic purposes
 1154        public override string ToString() =>
 01155            $"{nameof(HttpConnectionPool)} " +
 01156            (_proxyUri == null ?
 01157                (_sslOptionsHttp11 == null ?
 01158                    $"http://{_originAuthority}" :
 01159                    $"https://{_originAuthority}" + (_sslOptionsHttp11.TargetHost != _originAuthority.IdnHost ? $", SSL 
 01160                (_sslOptionsHttp11 == null ?
 01161                    $"Proxy {_proxyUri}" :
 01162                    $"https://{_originAuthority}/ tunnelled via Proxy {_proxyUri}" + (_sslOptionsHttp11.TargetHost != _o
 1163
 1164        public void Trace(string? message, [CallerMemberName] string? memberName = null) =>
 01165            NetEventSource.Log.HandlerMessage(
 01166                GetHashCode(),               // pool ID
 01167                0,                           // connection ID
 01168                0,                           // request ID
 01169                memberName,                  // method name
 01170                message);                    // message
 1171    }
 1172}
 1173

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http1.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers;
 5using System.Collections.Concurrent;
 6using System.Collections.Generic;
 7using System.Diagnostics;
 8using System.Diagnostics.CodeAnalysis;
 9using System.IO;
 10using System.Threading;
 11using System.Threading.Tasks;
 12
 13namespace System.Net.Http
 14{
 15    internal sealed partial class HttpConnectionPool
 16    {
 17        /// <summary>Stack of currently available HTTP/1.1 connections stored in the pool.</summary>
 018        private readonly ConcurrentStack<HttpConnection> _http11Connections = new();
 19        /// <summary>Controls whether we can use a fast path when returning connections to the pool and skip calling int
 20        private bool _http11RequestQueueIsEmptyAndNotDisposed;
 21        /// <summary>The maximum number of HTTP/1.1 connections allowed to be associated with the pool.</summary>
 22        private readonly int _maxHttp11Connections;
 23        /// <summary>The number of HTTP/1.1 connections associated with the pool, including in use, available, and pendi
 24        private int _associatedHttp11ConnectionCount;
 25        /// <summary>The number of HTTP/1.1 connections that are in the process of being established.</summary>
 26        private int _pendingHttp11ConnectionCount;
 27        /// <summary>Queue of requests waiting for an HTTP/1.1 connection.</summary>
 28        private RequestQueue<HttpConnection> _http11RequestQueue;
 29
 30        /// <summary>For non-proxy connection pools, this is the host name in bytes; for proxies, null.</summary>
 31        private readonly byte[]? _hostHeaderLineBytes;
 32
 033        public byte[]? HostHeaderLineBytes => _hostHeaderLineBytes;
 34
 35        private bool TryGetPooledHttp11Connection(HttpRequestMessage request, bool async, [NotNullWhen(true)] out HttpCo
 036        {
 037            while (_http11Connections.TryPop(out connection))
 038            {
 039                if (CheckExpirationOnGet(connection))
 040                {
 041                    if (NetEventSource.Log.IsEnabled()) connection.Trace("Found expired HTTP/1.1 connection in pool.");
 042                    connection.Dispose();
 043                    continue;
 44                }
 45
 046                if (!connection.PrepareForReuse(async))
 047                {
 048                    if (NetEventSource.Log.IsEnabled()) connection.Trace("Found invalid HTTP/1.1 connection in pool.");
 049                    connection.Dispose();
 050                    continue;
 51                }
 52
 053                if (NetEventSource.Log.IsEnabled()) connection.Trace("Found usable HTTP/1.1 connection in pool.");
 054                waiter = null;
 055                return true;
 56            }
 57
 58            // Slow path - no available connection found.
 59            // Push the request onto the request queue and check if we should inject a new connection.
 60
 061            waiter = new HttpConnectionWaiter<HttpConnection>();
 62
 63            // Technically this block under the lock could be a part of ProcessHttp11RequestQueue to avoid taking the lo
 64            // It is kept separate to simplify that method (avoid extra arguments that are only relevant for this caller
 065            lock (SyncObj)
 066            {
 067                _http11RequestQueue.EnqueueRequest(request, waiter);
 68
 69                // Disable the fast path and force connections returned to the pool to check the request queue first.
 070                _http11RequestQueueIsEmptyAndNotDisposed = false;
 071            }
 72
 73            // Other threads may have added a connection to the pool before we were able to
 74            // add the request to the queue, so we must check for an available connection again.
 75
 076            ProcessHttp11RequestQueue(null);
 077            return false;
 078        }
 79
 80        /// <summary>
 81        /// This method is called:
 82        /// <br/>- When returning a connection and observing that the request queue is not empty (<see cref="_http11Requ
 83        /// <br/>- After adding a request to the queue if we fail to obtain a connection from <see cref="_http11Connecti
 84        /// <br/>- After scavenging or disposing the pool to ensure that any pending requests are handled or connections
 85        /// <para>The method will attempt to match one request from the <see cref="_http11RequestQueue"/> to an availabl
 86        /// The <paramref name="connection"/> can either be provided as an argument (when returning a connection to the 
 87        /// As we'll only process a single request, we are expecting the method to be called every time a request is enq
 88        /// <para>If the <see cref="_http11RequestQueue"/> becomes empty, this method will reset the <see cref="_http11R
 89        /// such that returning connections will use the fast path again and skip calling into this method.</para>
 90        /// <para>Notably, this method will not be called on the fast path as long as we have enough connections to hand
 91        /// </summary>
 92        /// <param name="connection">The connection to use for a pending request, or return to the pool.</param>
 93        private void ProcessHttp11RequestQueue(HttpConnection? connection)
 094        {
 95            // Loop in case the request we try to signal was already cancelled or handled by a different connection.
 096            while (true)
 097            {
 098                HttpConnectionWaiter<HttpConnection>? waiter = null;
 99
 0100                lock (SyncObj)
 0101                {
 102#if DEBUG
 103                    // Other threads may still interact with the connections stack. Read the count once to keep the asse
 0104                    int connectionCount = _http11Connections.Count;
 0105                    Debug.Assert(_associatedHttp11ConnectionCount >= connectionCount + _pendingHttp11ConnectionCount,
 0106                        $"Expected {_associatedHttp11ConnectionCount} >= {connectionCount} + {_pendingHttp11ConnectionCo
 107#endif
 0108                    Debug.Assert(_associatedHttp11ConnectionCount <= _maxHttp11Connections,
 0109                        $"Expected {_associatedHttp11ConnectionCount} <= {_maxHttp11Connections}");
 0110                    Debug.Assert(_associatedHttp11ConnectionCount >= _pendingHttp11ConnectionCount,
 0111                        $"Expected {_associatedHttp11ConnectionCount} >= {_pendingHttp11ConnectionCount}");
 112
 0113                    if (_http11RequestQueue.Count != 0)
 0114                    {
 0115                        if (connection is not null || _http11Connections.TryPop(out connection))
 0116                        {
 117                            // If the connection is new, this check will always succeed as there is no scavenging task p
 0118                            if (!connection.TryOwnScavengingTaskCompletion())
 0119                            {
 0120                                goto DisposeConnection;
 121                            }
 122
 123                            // TryDequeueWaiter will prune completed requests from the head of the queue,
 124                            // so it's possible for it to return false even though we checked that Count != 0.
 0125                            bool success = _http11RequestQueue.TryDequeueWaiter(this, out waiter);
 0126                            Debug.Assert(success == waiter is not null);
 0127                        }
 0128                    }
 129
 130                    // Update the empty queue flag now.
 131                    // If the request queue is now empty, returning connections will use the fast path and skip calling 
 0132                    _http11RequestQueueIsEmptyAndNotDisposed = _http11RequestQueue.Count == 0 && !_disposed;
 133
 0134                    if (waiter is null)
 0135                    {
 136                        // We didn't find a waiter to signal, or there were no connections available.
 137
 0138                        if (connection is not null)
 0139                        {
 140                            // A connection was provided to this method, or we rented one from the pool.
 141                            // Return it back to the pool since we're not going to use it yet.
 142
 143                            // We're returning it while holding the lock to avoid a scenario where
 144                            // - thread A sees no requests are waiting in the queue (current thread)
 145                            // - thread B adds a request to the queue, and sees no connections are available
 146                            // - thread A returns the connection to the pool
 147                            // We'd have both a connection and a request waiting in the pool, but nothing to pair the tw
 148
 149                            // The main scenario where we'll reach this branch is when we enqueue a request to the queue
 150                            // and set the _http11RequestQueueIsEmptyAndNotDisposed flag to false, followed by multiple
 151                            // returning connections observing the flag and calling into this method before we clear the
 152                            // This should be a relatively rare case, so the added contention should be minimal.
 153
 154                            // We took ownership of the scavenging task completion.
 155                            // If we can't return the completion (the task already completed), we must dispose the conne
 0156                            if (!connection.TryReturnScavengingTaskCompletionOwnership())
 0157                            {
 0158                                goto DisposeConnection;
 159                            }
 160
 0161                            _http11Connections.Push(connection);
 0162                        }
 163                        else
 0164                        {
 165                            // We may be out of available connections, check if we should inject a new one.
 0166                            CheckForHttp11ConnectionInjection();
 0167                        }
 168
 0169                        break;
 170                    }
 0171                }
 172
 0173                Debug.Assert(connection is not null);
 174
 0175                if (waiter.TrySignal(connection))
 0176                {
 177                    // Success. Note that we did not call connection.PrepareForReuse
 178                    // before signaling the waiter. This is intentional, as the fact that
 179                    // this method was called indicates that the connection is either new,
 180                    // or was just returned to the pool and is still in a good state.
 181                    //
 182                    // We must, however, take ownership of the scavenging task completion as
 183                    // there is a small chance that such a task was started if the connection
 184                    // was briefly returned to the pool.
 0185                    return;
 186                }
 187
 188                // The request was already cancelled or handled by a different connection.
 189
 190                // We took ownership of the scavenging task completion.
 191                // If we can't return the completion (the task already completed), we must dispose the connection.
 0192                if (!connection.TryReturnScavengingTaskCompletionOwnership())
 0193                {
 0194                    goto DisposeConnection;
 195                }
 196
 197                // Loop again to try to find another request to signal, or return the connection.
 0198                continue;
 199
 0200            DisposeConnection:
 201                // The scavenging task completed before we assigned a request to the connection.
 202                // We've received EOF/erroneous data and the connection is not usable anymore.
 203                // Throw it away and try again.
 0204                connection.Dispose();
 0205                connection = null;
 0206            }
 207
 0208            if (_disposed)
 0209            {
 210                // The pool is being disposed and there are no more requests to handle.
 211                // Clean up any idle connections still waiting in the pool.
 0212                while (_http11Connections.TryPop(out connection))
 0213                {
 0214                    connection.Dispose();
 0215                }
 0216            }
 0217        }
 218
 219        private void CheckForHttp11ConnectionInjection()
 0220        {
 0221            Debug.Assert(HasSyncObjLock);
 222
 0223            _http11RequestQueue.PruneCompletedRequestsFromHeadOfQueue(this);
 224
 225            // Determine if we can and should add a new connection to the pool.
 0226            bool willInject =
 0227                _http11RequestQueue.Count > _pendingHttp11ConnectionCount &&    // More requests queued than pending con
 0228                _associatedHttp11ConnectionCount < _maxHttp11Connections &&     // Under the connection limit
 0229                _http11RequestQueue.RequestsWithoutAConnectionAttempt > 0;      // There are requests we haven't issued 
 230
 0231            if (NetEventSource.Log.IsEnabled())
 0232            {
 0233                Trace($"Available HTTP/1.1 connections: {_http11Connections.Count}, Requests in the queue: {_http11Reque
 0234                    $"Requests without a connection attempt: {_http11RequestQueue.RequestsWithoutAConnectionAttempt}, " 
 0235                    $"Pending HTTP/1.1 connections: {_pendingHttp11ConnectionCount}, Total associated HTTP/1.1 connectio
 0236                    $"Max HTTP/1.1 connection limit: {_maxHttp11Connections}, " +
 0237                    $"Will inject connection: {willInject}.");
 0238            }
 239
 0240            if (willInject)
 0241            {
 0242                _associatedHttp11ConnectionCount++;
 0243                _pendingHttp11ConnectionCount++;
 244
 0245                RequestQueue<HttpConnection>.QueueItem queueItem = _http11RequestQueue.PeekNextRequestForConnectionAttem
 0246                _ = InjectNewHttp11ConnectionAsync(queueItem); // ignore returned task
 0247            }
 0248        }
 249
 250        private async Task InjectNewHttp11ConnectionAsync(RequestQueue<HttpConnection>.QueueItem queueItem)
 0251        {
 0252            if (NetEventSource.Log.IsEnabled()) Trace("Creating new HTTP/1.1 connection for pool.");
 253
 254            // Queue the remainder of the work so that this method completes quickly
 255            // and escapes locks held by the caller.
 0256            await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
 257
 0258            HttpConnectionWaiter<HttpConnection> waiter = queueItem.Waiter;
 0259            HttpConnection? connection = null;
 0260            Exception? connectionException = null;
 261
 0262            CancellationTokenSource cts = GetConnectTimeoutCancellationTokenSource(waiter);
 263            try
 0264            {
 0265                connection = await CreateHttp11ConnectionAsync(queueItem.Request, true, cts.Token).ConfigureAwait(false)
 0266            }
 0267            catch (Exception e)
 0268            {
 0269                connectionException = e is OperationCanceledException oce && oce.CancellationToken == cts.Token && !wait
 0270                    CreateConnectTimeoutException(oce) :
 0271                    e;
 0272            }
 273            finally
 0274            {
 0275                lock (waiter)
 0276                {
 0277                    waiter.ConnectionCancellationTokenSource = null;
 0278                    cts.Dispose();
 0279                }
 0280            }
 281
 0282            if (connection is not null)
 0283            {
 284                // Add the established connection to the pool.
 0285                AddNewHttp11Connection(connection, queueItem.Waiter);
 0286            }
 287            else
 0288            {
 0289                Debug.Assert(connectionException is not null);
 0290                HandleHttp11ConnectionFailure(waiter, connectionException);
 0291            }
 0292        }
 293
 294        internal async ValueTask<HttpConnection> CreateHttp11ConnectionAsync(HttpRequestMessage request, bool async, Can
 0295        {
 0296            (Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint, long con
 0297            return await ConstructHttp11ConnectionAsync(async, stream, transportContext, request, activity, remoteEndPoi
 0298        }
 299
 300        private async ValueTask<HttpConnection> ConstructHttp11ConnectionAsync(bool async, Stream stream, TransportConte
 0301        {
 0302            Stream newStream = await ApplyPlaintextFilterAsync(async, stream, HttpVersion.Version11, request, connection
 0303            return new HttpConnection(this, newStream, transportContext, activity, remoteEndPoint, connectionId);
 0304        }
 305
 306        private void HandleHttp11ConnectionFailure(HttpConnectionWaiter<HttpConnection>? requestWaiter, Exception e)
 0307        {
 0308            if (NetEventSource.Log.IsEnabled()) Trace($"HTTP/1.1 connection failed: {e}");
 309
 310            // If this is happening as part of an HTTP/2 => HTTP/1.1 downgrade, we won't have an HTTP/1.1 waiter associa
 311            // We don't care if this fails; that means the request was previously canceled or handled by a different con
 0312            requestWaiter?.TrySetException(e);
 313
 0314            lock (SyncObj)
 0315            {
 0316                Debug.Assert(_associatedHttp11ConnectionCount > 0);
 0317                Debug.Assert(_pendingHttp11ConnectionCount > 0);
 318
 0319                _associatedHttp11ConnectionCount--;
 0320                _pendingHttp11ConnectionCount--;
 321
 0322                CheckForHttp11ConnectionInjection();
 0323            }
 0324        }
 325
 326        public void RecycleHttp11Connection(HttpConnection connection)
 0327        {
 0328            if (CheckExpirationOnReturn(connection))
 0329            {
 0330                if (NetEventSource.Log.IsEnabled()) connection.Trace("Disposing HTTP/1.1 connection when returning to po
 0331                connection.Dispose();
 0332                return;
 333            }
 334
 0335            ReturnHttp11Connection(connection);
 0336        }
 337
 338        private void AddNewHttp11Connection(HttpConnection connection, HttpConnectionWaiter<HttpConnection>? initialRequ
 0339        {
 0340            if (NetEventSource.Log.IsEnabled()) Trace("");
 341
 0342            lock (SyncObj)
 0343            {
 0344                Debug.Assert(_pendingHttp11ConnectionCount > 0);
 0345                _pendingHttp11ConnectionCount--;
 346
 0347                if (initialRequestWaiter is not null)
 0348                {
 349                    // If we're about to signal the initial waiter, that request must be removed from the queue if it wa
 350                    // Normally, TryDequeueWaiter would handle the removal. TryDequeueSpecificWaiter matches this behavi
 351                    // We don't care if this fails; that means the request was previously canceled, handled by a differe
 0352                    _http11RequestQueue.TryDequeueSpecificWaiter(initialRequestWaiter);
 353
 354                    // There's no need for us to hold the lock while signaling the waiter.
 0355                }
 0356            }
 357
 0358            if (initialRequestWaiter is not null &&
 0359                initialRequestWaiter.TrySignal(connection))
 0360            {
 0361                return;
 362            }
 363
 0364            ReturnHttp11Connection(connection);
 0365        }
 366
 367        private void ReturnHttp11Connection(HttpConnection connection)
 0368        {
 0369            connection.MarkConnectionAsIdle();
 370
 371            // Eviction callback checks are normally triggered from a background timer that looks at all idle connection
 372            // If this connection was in use during that time, the eviction callback may have been skipped for it.
 373            // Check whether that's the case now by comparing the last EvictionGeneration of the connection with that of
 0374            connection.RunEvictionEvaluationIfNeeded();
 375
 376            // The fast path when there are enough connections and no pending requests
 377            // is that we'll see _http11RequestQueueIsEmptyAndNotDisposed being true both
 378            // times, and all we'll have to do as part of returning the connection is
 379            // a Push call on the concurrent stack.
 380
 0381            if (Volatile.Read(ref _http11RequestQueueIsEmptyAndNotDisposed))
 0382            {
 0383                _http11Connections.Push(connection);
 384
 385                // When we add a connection to the pool, we must ensure that there are
 386                // either no pending requests waiting, or that _something_ will pair those
 387                // requests with the connection we just added.
 388
 389                // When adding a request to the queue, we'll first check if there's
 390                // an available connection waiting in the pool that we could use.
 391                // If there isn't, we'll set the _http11RequestQueueIsEmptyAndNotDisposed
 392                // flag and check for available connections again.
 393
 394                // To avoid a race where we add the connection after a request was enqueued,
 395                // we'll check the flag again and try to process one request from the queue.
 396
 0397                if (!Volatile.Read(ref _http11RequestQueueIsEmptyAndNotDisposed))
 0398                {
 0399                    ProcessHttp11RequestQueue(null);
 0400                }
 0401            }
 402            else
 0403            {
 404                // ProcessHttp11RequestQueue is responsible for handing the connection to a pending request,
 405                // or to return it back to the pool if there aren't any.
 406
 407                // We hand over the connection directly instead of pushing it on the stack first to ensure
 408                // that pending requests are processed in a fair (FIFO) order.
 0409                ProcessHttp11RequestQueue(connection);
 0410            }
 0411        }
 412
 413        /// <summary>
 414        /// Called when an HttpConnection from this pool is no longer usable.
 415        /// Note, this is always called from HttpConnection.Dispose, which is a bit different than how HTTP2 works.
 416        /// </summary>
 417        public void InvalidateHttp11Connection(HttpConnection connection, bool disposing = true)
 0418        {
 0419            lock (SyncObj)
 0420            {
 0421                Debug.Assert(_associatedHttp11ConnectionCount > 0);
 0422                Debug.Assert(!disposing || Array.IndexOf(_http11Connections.ToArray(), connection) < 0);
 423
 0424                _associatedHttp11ConnectionCount--;
 425
 0426                CheckForHttp11ConnectionInjection();
 0427            }
 0428        }
 429
 430        private static void ScavengeHttp11ConnectionStack(HttpConnectionPool pool, ConcurrentStack<HttpConnection> conne
 0431        {
 432            // We can't simply enumerate the connections stack as other threads may still be adding and removing entries
 433            // If we want to check the state of a connection, we must take it from the stack first to ensure we own it.
 434
 435            // We're about to starve the connection pool of all available connections for a moment.
 436            // We must be holding the lock while doing so to ensure that any new requests that
 437            // come in during this time will be blocked waiting in ProcessHttp11RequestQueue.
 438            // If this were not the case, requests would repeatedly call into CheckForHttp11ConnectionInjection
 439            // and trigger new connection attempts, even if we have enough connections in our copy.
 0440            Debug.Assert(pool.HasSyncObjLock);
 0441            Debug.Assert(connections.Count <= pool._associatedHttp11ConnectionCount);
 442
 0443            HttpConnection[] stackCopy = ArrayPool<HttpConnection>.Shared.Rent(pool._associatedHttp11ConnectionCount);
 0444            int usableConnections = 0;
 445
 0446            while (connections.TryPop(out HttpConnection? connection))
 0447            {
 0448                if (connection.IsUsable(nowTicks, pooledConnectionLifetime, pooledConnectionIdleTimeout))
 0449                {
 0450                    stackCopy[usableConnections++] = connection;
 0451                }
 452                else
 0453                {
 0454                    toDispose ??= new List<HttpConnectionBase>();
 0455                    toDispose.Add(connection);
 0456                }
 0457            }
 458
 0459            if (usableConnections > 0)
 0460            {
 461                // Add them back in reverse to maintain the LIFO order.
 0462                Span<HttpConnection> usable = stackCopy.AsSpan(0, usableConnections);
 0463                usable.Reverse();
 0464                connections.PushRange(stackCopy, 0, usableConnections);
 0465                usable.Clear();
 0466            }
 467
 0468            ArrayPool<HttpConnection>.Shared.Return(stackCopy);
 0469        }
 470    }
 471}
 472

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http2.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Globalization;
 8using System.IO;
 9using System.Net.Security;
 10using System.Runtime.ExceptionServices;
 11using System.Security.Authentication;
 12using System.Text;
 13using System.Threading;
 14using System.Threading.Tasks;
 15
 16namespace System.Net.Http
 17{
 18    internal sealed partial class HttpConnectionPool
 19    {
 20        /// <summary>List of available HTTP/2 connections stored in the pool.</summary>
 21        private List<Http2Connection>? _availableHttp2Connections;
 22        /// <summary>
 23        /// HTTP/2 connections created by this pool that haven't completed their teardown yet.
 24        /// Unlike <see cref="_availableHttp2Connections"/>, this also includes connections that reached
 25        /// their stream limit, or that are shutting down (e.g. after a GOAWAY frame) but are still
 26        /// processing requests. Only tracked if keep alive pings are enabled, as this list exists
 27        /// solely to keep <see cref="HeartBeat"/> working for the whole lifetime of a connection.
 28        /// </summary>
 29        private List<Http2Connection>? _http2ConnectionsForHeartBeat;
 30        /// <summary>The number of HTTP/2 connections associated with the pool, including in use, available, and pending
 31        private int _associatedHttp2ConnectionCount;
 32        /// <summary>Indicates whether an HTTP/2 connection is in the process of being established.</summary>
 33        private bool _pendingHttp2Connection;
 34        /// <summary>Queue of requests waiting for an HTTP/2 connection.</summary>
 35        private RequestQueue<Http2Connection?> _http2RequestQueue;
 36
 37        private bool _http2Enabled;
 38        private bool _http2SessionAuthSeen;
 39        private byte[]? _http2AltSvcOriginUri;
 40        internal readonly byte[]? _http2EncodedAuthorityHostHeader;
 41
 42        /// <summary>
 43        /// An ASCII origin string per RFC 6454 Section 6.2, in format &lt;scheme&gt;://&lt;host&gt;[:&lt;port&gt;]
 44        /// </summary>
 45        /// <remarks>
 46        /// Used by <see cref="Http2Connection"/> to test ALTSVC frames for our origin.
 47        /// </remarks>
 48        public byte[] Http2AltSvcOriginUri
 49        {
 50            get
 051            {
 052                if (_http2AltSvcOriginUri == null)
 053                {
 054                    var sb = new StringBuilder();
 55
 056                    sb.Append(IsSecure ? "https://" : "http://")
 057                      .Append(_originAuthority.IdnHost);
 58
 059                    if (!IsDefaultPort)
 060                    {
 061                        sb.Append(CultureInfo.InvariantCulture, $":{_originAuthority.Port}");
 062                    }
 63
 064                    _http2AltSvcOriginUri = Encoding.ASCII.GetBytes(sb.ToString());
 065                }
 66
 067                return _http2AltSvcOriginUri;
 068            }
 69        }
 70
 071        private bool EnableMultipleHttp2Connections => _poolManager.Settings.EnableMultipleHttp2Connections;
 72
 73        private bool TryGetPooledHttp2Connection(HttpRequestMessage request, [NotNullWhen(true)] out Http2Connection? co
 074        {
 075            Debug.Assert(_kind is HttpConnectionKind.Https or HttpConnectionKind.SslProxyTunnel or HttpConnectionKind.Ht
 76
 77            // Look for a usable connection.
 078            while (true)
 079            {
 080                lock (SyncObj)
 081                {
 082                    if (!_http2Enabled)
 083                    {
 084                        waiter = null;
 085                        connection = null;
 086                        return false;
 87                    }
 88
 089                    int availableConnectionCount = _availableHttp2Connections?.Count ?? 0;
 090                    if (availableConnectionCount > 0)
 091                    {
 92                        // We have a connection that we can attempt to use.
 93                        // Validate it below outside the lock, to avoid doing expensive operations while holding the loc
 094                        connection = _availableHttp2Connections![availableConnectionCount - 1];
 095                    }
 96                    else
 097                    {
 98                        // No available connections. Add to the request queue.
 099                        waiter = _http2RequestQueue.EnqueueRequest(request);
 100
 0101                        CheckForHttp2ConnectionInjection();
 102
 103                        // There were no available connections. This request has been added to the request queue.
 0104                        if (NetEventSource.Log.IsEnabled()) Trace($"No available HTTP/2 connections; request queued.");
 0105                        connection = null;
 0106                        return false;
 107                    }
 0108                }
 109
 0110                if (CheckExpirationOnGet(connection))
 0111                {
 0112                    if (NetEventSource.Log.IsEnabled()) connection.Trace("Found expired HTTP/2 connection in pool.");
 113
 0114                    InvalidateHttp2Connection(connection);
 0115                    continue;
 116                }
 117
 0118                if (!connection.TryReserveStream())
 0119                {
 0120                    if (NetEventSource.Log.IsEnabled()) connection.Trace("Found HTTP/2 connection in pool without availa
 121
 0122                    bool found = false;
 0123                    lock (SyncObj)
 0124                    {
 0125                        int index = _availableHttp2Connections.IndexOf(connection);
 0126                        if (index != -1)
 0127                        {
 0128                            found = true;
 0129                            _availableHttp2Connections.RemoveAt(index);
 0130                        }
 0131                    }
 132
 133                    // If we didn't find the connection, then someone beat us to removing it (or it shut down)
 0134                    if (found)
 0135                    {
 0136                        DisableHttp2Connection(connection);
 0137                    }
 0138                    continue;
 139                }
 140
 0141                if (NetEventSource.Log.IsEnabled()) connection.Trace("Found usable HTTP/2 connection in pool.");
 0142                waiter = null;
 0143                return true;
 144            }
 0145        }
 146
 147        private void CheckForHttp2ConnectionInjection()
 0148        {
 0149            Debug.Assert(HasSyncObjLock);
 150
 0151            _http2RequestQueue.PruneCompletedRequestsFromHeadOfQueue(this);
 152
 153            // Determine if we can and should add a new connection to the pool.
 0154            int availableHttp2ConnectionCount = _availableHttp2Connections?.Count ?? 0;
 0155            bool willInject = availableHttp2ConnectionCount == 0 &&                         // No available connections
 0156                !_pendingHttp2Connection &&                                                 // Only allow one pending HT
 0157                _http2RequestQueue.Count > 0 &&                                             // There are requests left o
 0158                (_associatedHttp2ConnectionCount == 0 || EnableMultipleHttp2Connections) && // We allow multiple connect
 0159                _http2RequestQueue.RequestsWithoutAConnectionAttempt > 0;                   // There are requests we hav
 160
 0161            if (NetEventSource.Log.IsEnabled())
 0162            {
 0163                Trace($"Available HTTP/2.0 connections: {availableHttp2ConnectionCount}, " +
 0164                    $"Pending HTTP/2.0 connection: {_pendingHttp2Connection}, " +
 0165                    $"Requests in the queue: {_http2RequestQueue.Count}, " +
 0166                    $"Requests without a connection attempt: {_http2RequestQueue.RequestsWithoutAConnectionAttempt}, " +
 0167                    $"Total associated HTTP/2.0 connections: {_associatedHttp2ConnectionCount}, " +
 0168                    $"Will inject connection: {willInject}.");
 0169            }
 170
 0171            if (willInject)
 0172            {
 0173                _associatedHttp2ConnectionCount++;
 0174                _pendingHttp2Connection = true;
 175
 0176                RequestQueue<Http2Connection?>.QueueItem queueItem = _http2RequestQueue.PeekNextRequestForConnectionAtte
 0177                _ = InjectNewHttp2ConnectionAsync(queueItem); // ignore returned task
 0178            }
 0179        }
 180
 181        private async Task InjectNewHttp2ConnectionAsync(RequestQueue<Http2Connection?>.QueueItem queueItem)
 0182        {
 0183            if (NetEventSource.Log.IsEnabled()) Trace("Creating new HTTP/2 connection for pool.");
 184
 185            // Queue the remainder of the work so that this method completes quickly
 186            // and escapes locks held by the caller.
 0187            await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
 188
 0189            Http2Connection? connection = null;
 0190            Exception? connectionException = null;
 0191            HttpConnectionWaiter<Http2Connection?> waiter = queueItem.Waiter;
 192
 0193            CancellationTokenSource cts = GetConnectTimeoutCancellationTokenSource(waiter);
 194            try
 0195            {
 0196                (Stream stream, TransportContext? transportContext, Activity? activity, IPEndPoint? remoteEndPoint, long
 197
 0198                if (IsSecure)
 0199                {
 0200                    SslStream sslStream = (SslStream)stream;
 201
 0202                    if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
 0203                    {
 204                        // The server accepted our request for HTTP2.
 205
 0206                        if (sslStream.SslProtocol < SslProtocols.Tls12)
 0207                        {
 0208                            stream.Dispose();
 0209                            connectionException = new HttpRequestException(SR.Format(SR.net_ssl_http2_requires_tls12, ss
 0210                        }
 211                        else
 0212                        {
 0213                            connection = await ConstructHttp2ConnectionAsync(stream, queueItem.Request, activity, remote
 0214                        }
 0215                    }
 216                    else
 0217                    {
 218                        // We established an SSL connection, but the server denied our request for HTTP2.
 0219                        await HandleHttp11Downgrade(queueItem.Request, stream, transportContext, activity, remoteEndPoin
 0220                        return;
 221                    }
 0222                }
 223                else
 0224                {
 0225                    connection = await ConstructHttp2ConnectionAsync(stream, queueItem.Request, activity, remoteEndPoint
 0226                }
 0227            }
 0228            catch (Exception e)
 0229            {
 0230                connectionException = e is OperationCanceledException oce && oce.CancellationToken == cts.Token && !wait
 0231                    CreateConnectTimeoutException(oce) :
 0232                    e;
 0233            }
 234            finally
 0235            {
 0236                lock (waiter)
 0237                {
 0238                    waiter.ConnectionCancellationTokenSource = null;
 0239                    cts.Dispose();
 0240                }
 0241            }
 242
 0243            if (connection is not null)
 0244            {
 245                // Add the new connection to the pool.
 0246                ReturnHttp2Connection(connection, isNewConnection: true, queueItem.Waiter);
 0247            }
 248            else
 0249            {
 0250                Debug.Assert(connectionException is not null);
 0251                HandleHttp2ConnectionFailure(waiter, connectionException);
 0252            }
 0253        }
 254
 255        private async ValueTask<Http2Connection> ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request
 0256        {
 0257            stream = await ApplyPlaintextFilterAsync(async: true, stream, HttpVersion.Version20, request, connectionId, 
 258
 0259            Http2Connection http2Connection = new Http2Connection(this, stream, activity, remoteEndPoint, connectionId);
 260            try
 0261            {
 0262                await http2Connection.SetupAsync(cancellationToken).ConfigureAwait(false);
 0263            }
 0264            catch (Exception e)
 0265            {
 266                // Note, SetupAsync will dispose the connection if there is an exception.
 0267                if (e is OperationCanceledException oce && oce.CancellationToken == cancellationToken)
 0268                {
 269                    // Note, AddHttp2ConnectionAsync handles this OCE separately so don't wrap it.
 0270                    throw;
 271                }
 272
 0273                throw new HttpRequestException(SR.net_http_client_execution_error, e);
 274            }
 275
 0276            return http2Connection;
 0277        }
 278
 279        private void HandleHttp2ConnectionFailure(HttpConnectionWaiter<Http2Connection?> requestWaiter, Exception e)
 0280        {
 0281            if (NetEventSource.Log.IsEnabled()) Trace($"HTTP2 connection failed: {e}");
 282
 283            // We don't care if this fails; that means the request was previously canceled or handled by a different con
 0284            requestWaiter.TrySetException(e);
 285
 0286            lock (SyncObj)
 0287            {
 0288                Debug.Assert(_associatedHttp2ConnectionCount > 0);
 0289                Debug.Assert(_pendingHttp2Connection);
 290
 0291                _associatedHttp2ConnectionCount--;
 0292                _pendingHttp2Connection = false;
 293
 0294                CheckForHttp2ConnectionInjection();
 0295            }
 0296        }
 297
 298        /// <summary>
 299        /// Marks this pool as having seen a session-based authentication challenge on HTTP/2.
 300        /// Future requests that can fall back to HTTP/1.1 (see <see cref="CanFallBackToHttp11"/>)
 301        /// will skip HTTP/2 and go directly to HTTP/1.1.
 302        /// Requests that require HTTP/2 (e.g., <see cref="HttpVersionPolicy.RequestVersionExact"/>
 303        /// with <see cref="HttpRequestMessage.Version"/> >= 2.0) continue to use HTTP/2 as before.
 304        /// </summary>
 305        internal void OnSessionAuthenticationChallengeSeen()
 0306        {
 0307            _http2SessionAuthSeen = true;
 0308        }
 309
 310        private async Task HandleHttp11Downgrade(HttpRequestMessage request, Stream stream, TransportContext? transportC
 0311        {
 0312            if (NetEventSource.Log.IsEnabled()) Trace("Server does not support HTTP2; disabling HTTP2 use and proceeding
 313
 0314            bool canUse = true;
 0315            HttpConnectionWaiter<Http2Connection?>? waiter = null;
 0316            lock (SyncObj)
 0317            {
 0318                Debug.Assert(_pendingHttp2Connection);
 0319                Debug.Assert(_associatedHttp2ConnectionCount > 0);
 320
 321                // Server does not support HTTP2. Disable further HTTP2 attempts.
 0322                _http2Enabled = false;
 0323                _associatedHttp2ConnectionCount--;
 0324                _pendingHttp2Connection = false;
 325
 0326                if (_associatedHttp11ConnectionCount < _maxHttp11Connections)
 0327                {
 0328                    _associatedHttp11ConnectionCount++;
 0329                    _pendingHttp11ConnectionCount++;
 0330                }
 331                else
 0332                {
 333                    // We are already at the limit for HTTP/1.1 connections, so do not proceed with this connection.
 0334                    canUse = false;
 0335                }
 336
 0337                _http2RequestQueue.TryDequeueWaiter(this, out waiter);
 0338            }
 339
 340            // Signal to any queued HTTP2 requests that they must downgrade.
 0341            while (waiter is not null)
 0342            {
 0343                if (NetEventSource.Log.IsEnabled()) Trace("Downgrading queued HTTP2 request to HTTP/1.1");
 344
 345                // We are done with the HTTP2 connection attempt, no point to cancel it.
 0346                Volatile.Write(ref waiter.ConnectionCancellationTokenSource, null);
 347
 348                // We don't care if this fails; that means the request was previously canceled or handled by a different
 0349                waiter.TrySetResult(null);
 350
 0351                lock (SyncObj)
 0352                {
 0353                    _http2RequestQueue.TryDequeueWaiter(this, out waiter);
 0354                }
 0355            }
 356
 0357            if (!canUse)
 0358            {
 0359                if (NetEventSource.Log.IsEnabled()) Trace("Discarding downgraded HTTP/1.1 connection because HTTP/1.1 co
 0360                stream.Dispose();
 0361                return;
 362            }
 363
 364            HttpConnection http11Connection;
 365            try
 0366            {
 367                // Note, the same CancellationToken from the original HTTP2 connection establishment still applies here.
 0368                http11Connection = await ConstructHttp11ConnectionAsync(true, stream, transportContext, request, activit
 0369            }
 0370            catch (OperationCanceledException oce) when (oce.CancellationToken == cancellationToken)
 0371            {
 0372                HandleHttp11ConnectionFailure(requestWaiter: null, CreateConnectTimeoutException(oce));
 0373                return;
 374            }
 0375            catch (Exception e)
 0376            {
 0377                HandleHttp11ConnectionFailure(requestWaiter: null, e);
 0378                return;
 379            }
 380
 0381            AddNewHttp11Connection(http11Connection, initialRequestWaiter: null);
 0382        }
 383
 384        private void ReturnHttp2Connection(Http2Connection connection, bool isNewConnection, HttpConnectionWaiter<Http2C
 0385        {
 0386            if (NetEventSource.Log.IsEnabled()) connection.Trace($"{nameof(isNewConnection)}={isNewConnection}");
 387
 0388            Debug.Assert(!HasSyncObjLock);
 0389            Debug.Assert(isNewConnection || initialRequestWaiter is null, "Shouldn't have a request unless the connectio
 390
 0391            if (!isNewConnection && CheckExpirationOnReturn(connection))
 0392            {
 0393                lock (SyncObj)
 0394                {
 0395                    Debug.Assert(_availableHttp2Connections is null || !_availableHttp2Connections.Contains(connection))
 0396                    Debug.Assert(_associatedHttp2ConnectionCount > (_availableHttp2Connections?.Count ?? 0));
 0397                    _associatedHttp2ConnectionCount--;
 0398                }
 399
 0400                if (NetEventSource.Log.IsEnabled()) connection.Trace("Disposing HTTP2 connection return to pool. Connect
 0401                connection.Dispose();
 0402                return;
 403            }
 404
 0405            while (connection.TryReserveStream())
 0406            {
 407                // Loop in case we get a request that has already been canceled or handled by a different connection.
 0408                while (true)
 0409                {
 0410                    HttpConnectionWaiter<Http2Connection?>? waiter = null;
 0411                    bool added = false;
 0412                    lock (SyncObj)
 0413                    {
 0414                        Debug.Assert(_availableHttp2Connections is null || !_availableHttp2Connections.Contains(connecti
 0415                        Debug.Assert(_associatedHttp2ConnectionCount > (_availableHttp2Connections?.Count ?? 0),
 0416                            $"Expected _associatedHttp2ConnectionCount={_associatedHttp2ConnectionCount} > _availableHtt
 417
 0418                        if (isNewConnection)
 0419                        {
 0420                            Debug.Assert(_pendingHttp2Connection);
 0421                            _pendingHttp2Connection = false;
 0422                            isNewConnection = false;
 0423                        }
 424
 0425                        if (initialRequestWaiter is not null)
 0426                        {
 427                            // Try to handle the request that we initiated the connection for first
 0428                            waiter = initialRequestWaiter;
 0429                            initialRequestWaiter = null;
 430
 431                            // If this method found a request to service, that request must be removed from the queue if
 432                            // Normally, TryDequeueWaiter would handle the removal. TryDequeueSpecificWaiter matches thi
 433                            // We don't care if this fails; that means the request was previously canceled, handled by a
 0434                            _http2RequestQueue.TryDequeueSpecificWaiter(waiter);
 0435                        }
 0436                        else if (_http2RequestQueue.TryDequeueWaiter(this, out waiter))
 0437                        {
 0438                            Debug.Assert((_availableHttp2Connections?.Count ?? 0) == 0, $"With {(_availableHttp2Connecti
 0439                        }
 0440                        else if (_disposed)
 0441                        {
 442                            // The pool has been disposed. We will dispose this connection below outside the lock.
 443                            // We do this check after processing the request queue so that any queued requests will be h
 0444                            _associatedHttp2ConnectionCount--;
 0445                        }
 446                        else
 0447                        {
 448                            // Add connection to the pool.
 0449                            added = true;
 0450                            _availableHttp2Connections ??= new List<Http2Connection>();
 0451                            _availableHttp2Connections.Add(connection);
 0452                        }
 0453                    }
 454
 0455                    if (waiter is not null)
 0456                    {
 0457                        Debug.Assert(!added);
 458
 0459                        if (waiter.TrySignal(connection))
 0460                        {
 0461                            break;
 462                        }
 463
 464                        // Loop and process the queue again
 0465                    }
 466                    else
 0467                    {
 0468                        connection.ReleaseStream();
 0469                        if (added)
 0470                        {
 0471                            if (NetEventSource.Log.IsEnabled()) connection.Trace("Put HTTP2 connection in pool.");
 0472                            return;
 473                        }
 474                        else
 0475                        {
 0476                            Debug.Assert(_disposed);
 0477                            if (NetEventSource.Log.IsEnabled()) connection.Trace("Disposing HTTP2 connection returned to
 0478                            connection.Dispose();
 0479                            return;
 480                        }
 481                    }
 0482                }
 0483            }
 484
 0485            if (isNewConnection)
 0486            {
 0487                Debug.Assert(initialRequestWaiter is not null, "Expect request for a new connection");
 488
 489                // The new connection could not handle even one request, either because it shut down before we could use
 490                // or because it immediately set the max concurrent streams limit to 0.
 491                // We don't want to get stuck in a loop where we keep trying to create new connections for the same requ
 492                // So, treat this as a connection failure.
 493
 0494                if (NetEventSource.Log.IsEnabled()) connection.Trace("New HTTP2 connection is unusable due to no availab
 0495                connection.Dispose();
 496
 0497                HttpRequestException hre = new HttpRequestException(SR.net_http_http2_connection_not_established);
 0498                ExceptionDispatchInfo.SetCurrentStackTrace(hre);
 0499                HandleHttp2ConnectionFailure(initialRequestWaiter, hre);
 0500            }
 501            else
 0502            {
 503                // Since we only inject one connection at a time, we may want to inject another now.
 0504                lock (SyncObj)
 0505                {
 0506                    CheckForHttp2ConnectionInjection();
 0507                }
 508
 509                // We need to wait until the connection is usable again.
 0510                DisableHttp2Connection(connection);
 0511            }
 0512        }
 513
 514        /// <summary>
 515        /// Disable usage of the specified connection because it cannot handle any more streams at the moment.
 516        /// We will register to be notified when it can handle more streams (or becomes permanently unusable).
 517        /// </summary>
 518        private void DisableHttp2Connection(Http2Connection connection)
 0519        {
 0520            if (NetEventSource.Log.IsEnabled()) connection.Trace("");
 521
 0522            _ = DisableHttp2ConnectionAsync(connection); // ignore returned task
 523
 524            async Task DisableHttp2ConnectionAsync(Http2Connection connection)
 0525            {
 0526                bool usable = await connection.WaitForAvailableStreamsAsync().ConfigureAwait(ConfigureAwaitOptions.Force
 527
 0528                if (NetEventSource.Log.IsEnabled()) connection.Trace($"{nameof(connection.WaitForAvailableStreamsAsync)}
 529
 0530                if (usable)
 0531                {
 0532                    ReturnHttp2Connection(connection, isNewConnection: false);
 0533                }
 534                else
 0535                {
 536                    // Connection has shut down.
 0537                    lock (SyncObj)
 0538                    {
 0539                        Debug.Assert(_availableHttp2Connections is null || !_availableHttp2Connections.Contains(connecti
 0540                        Debug.Assert(_associatedHttp2ConnectionCount > 0);
 541
 0542                        _associatedHttp2ConnectionCount--;
 543
 0544                        CheckForHttp2ConnectionInjection();
 0545                    }
 546
 0547                    if (NetEventSource.Log.IsEnabled()) connection.Trace("HTTP2 connection no longer usable");
 0548                    connection.Dispose();
 0549                }
 0550            };
 0551        }
 552
 553        /// <summary>
 554        /// Called when an Http2Connection from this pool is no longer usable.
 555        /// </summary>
 556        public void InvalidateHttp2Connection(Http2Connection connection)
 0557        {
 0558            if (NetEventSource.Log.IsEnabled()) connection.Trace("");
 559
 0560            bool found = false;
 0561            lock (SyncObj)
 0562            {
 0563                if (_availableHttp2Connections is not null)
 0564                {
 0565                    Debug.Assert(_associatedHttp2ConnectionCount >= _availableHttp2Connections.Count);
 566
 0567                    int index = _availableHttp2Connections.IndexOf(connection);
 0568                    if (index != -1)
 0569                    {
 0570                        found = true;
 0571                        _availableHttp2Connections.RemoveAt(index);
 0572                        _associatedHttp2ConnectionCount--;
 0573                    }
 0574                }
 575
 0576                CheckForHttp2ConnectionInjection();
 0577            }
 578
 579            // If we found the connection in the available list, then dispose it now.
 580            // Otherwise, when we try to put it back in the pool, we will see it is shut down and dispose it (and adjust
 0581            if (found)
 0582            {
 0583                connection.Dispose();
 0584            }
 0585        }
 586
 587        /// <summary>Whether HTTP/2 connections in this pool should be sending keep alive PINGs.</summary>
 0588        private bool Http2KeepAlivePingEnabled => Settings._keepAlivePingDelay != Timeout.InfiniteTimeSpan;
 589
 590        /// <summary>
 591        /// Registers a newly created HTTP/2 connection with the pool so that it participates in <see cref="HeartBeat"/>
 592        /// Called from the <see cref="Http2Connection"/> constructor so that the connection is tracked even if it
 593        /// tears down before it's ever handed out to a request.
 594        /// </summary>
 595        public void AddHttp2ConnectionForHeartBeat(Http2Connection connection)
 0596        {
 0597            Debug.Assert(!HasSyncObjLock);
 598
 0599            if (!Http2KeepAlivePingEnabled)
 0600            {
 0601                return;
 602            }
 603
 0604            lock (SyncObj)
 0605            {
 0606                (_http2ConnectionsForHeartBeat ??= new List<Http2Connection>()).Add(connection);
 0607            }
 0608        }
 609
 610        /// <summary>Called when an HTTP/2 connection has completed its teardown and no longer needs heart beats.</summa
 611        public void RemoveHttp2ConnectionFromHeartBeat(Http2Connection connection)
 0612        {
 0613            Debug.Assert(!HasSyncObjLock);
 614
 0615            if (!Http2KeepAlivePingEnabled)
 0616            {
 0617                return;
 618            }
 619
 0620            lock (SyncObj)
 0621            {
 0622                bool removed = _http2ConnectionsForHeartBeat?.Remove(connection) ?? false;
 0623                Debug.Assert(removed);
 0624            }
 0625        }
 626
 627        /// <summary>
 628        /// Sends keep alive PINGs on all live HTTP/2 connections.
 629        /// Returns whether the pool may still have HTTP/2 connections that need heart beats.
 630        /// </summary>
 631        public bool HeartBeat()
 0632        {
 633            Http2Connection[]? localHttp2Connections;
 634            bool anyConnections;
 0635            lock (SyncObj)
 0636            {
 0637                localHttp2Connections = _http2ConnectionsForHeartBeat?.ToArray();
 638
 639                // Also account for connections that are still being established -- they aren't in the
 640                // list yet, but they will need heart beats as soon as they are.
 0641                anyConnections = localHttp2Connections is { Length: > 0 } || _associatedHttp2ConnectionCount > 0;
 0642            }
 643
 644            // Avoid calling HeartBeat under the lock, as it may call back into HttpConnectionPool.InvalidateHttp2Connec
 0645            if (localHttp2Connections is not null)
 0646            {
 0647                foreach (Http2Connection http2Connection in localHttp2Connections)
 0648                {
 0649                    http2Connection.HeartBeat();
 0650                }
 0651            }
 652
 0653            return anyConnections;
 0654        }
 655
 656        private static int ScavengeHttp2ConnectionList(List<Http2Connection> list, ref List<HttpConnectionBase>? toDispo
 0657        {
 0658            int freeIndex = 0;
 0659            while (freeIndex < list.Count && list[freeIndex].IsUsable(nowTicks, pooledConnectionLifetime, pooledConnecti
 0660            {
 0661                freeIndex++;
 0662            }
 663
 664            // If freeIndex == list.Count, nothing needs to be removed.
 665            // But if it's < list.Count, at least one connection needs to be purged.
 0666            int removed = 0;
 0667            if (freeIndex < list.Count)
 0668            {
 669                // We know the connection at freeIndex is unusable, so dispose of it.
 0670                toDispose ??= new List<HttpConnectionBase>();
 0671                toDispose.Add(list[freeIndex]);
 672
 673                // Find the first item after the one to be removed that should be kept.
 0674                int current = freeIndex + 1;
 0675                while (current < list.Count)
 0676                {
 677                    // Look for the first item to be kept.  Along the way, any
 678                    // that shouldn't be kept are disposed of.
 0679                    while (current < list.Count && !list[current].IsUsable(nowTicks, pooledConnectionLifetime, pooledCon
 0680                    {
 0681                        toDispose.Add(list[current]);
 0682                        current++;
 0683                    }
 684
 685                    // If we found something to keep, copy it down to the known free slot.
 0686                    if (current < list.Count)
 0687                    {
 688                        // copy item to the free slot
 0689                        list[freeIndex++] = list[current++];
 0690                    }
 691
 692                    // Keep going until there are no more good items.
 0693                }
 694
 695                // At this point, good connections have been moved below freeIndex, and garbage connections have
 696                // been added to the dispose list, so clear the end of the list past freeIndex.
 0697                removed = list.Count - freeIndex;
 0698                list.RemoveRange(freeIndex, removed);
 0699            }
 700
 0701            return removed;
 0702        }
 703    }
 704}
 705

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionPool/HttpConnectionPool.Http3.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Net.Http.Headers;
 8using System.Net.NetworkInformation;
 9using System.Net.Quic;
 10using System.Net.Security;
 11using System.Runtime.ExceptionServices;
 12using System.Runtime.Versioning;
 13using System.Threading;
 14using System.Threading.Tasks;
 15
 16namespace System.Net.Http
 17{
 18    internal sealed partial class HttpConnectionPool
 19    {
 20        /// <summary>
 21        /// If <see cref="_altSvcBlocklist"/> exceeds this size, Alt-Svc will be disabled entirely for <see cref="AltSvc
 22        /// This is to prevent a failing server from bloating the dictionary beyond a reasonable value.
 23        /// </summary>
 24        private const int MaxAltSvcIgnoreListSize = 8;
 25
 26        /// <summary>The time, in milliseconds, that an authority should remain in <see cref="_altSvcBlocklist"/>.</summ
 27        private const int AltSvcBlocklistTimeoutInMilliseconds = 10 * 60 * 1000;
 28
 29        /// <summary>List of available HTTP/3 connections stored in the pool.</summary>
 30        private List<Http3Connection>? _availableHttp3Connections;
 31        /// <summary>The number of HTTP/3 connections associated with the pool, including in use, available, and pending
 32        private int _associatedHttp3ConnectionCount;
 33        /// <summary>Indicates whether an HTTP/3 connection is in the process of being established.</summary>
 34        private bool _pendingHttp3Connection;
 35        /// <summary>Queue of requests waiting for an HTTP/3 connection.</summary>
 36        private RequestQueue<Http3Connection?> _http3RequestQueue;
 37
 38        private bool _http3Enabled;
 39        internal readonly byte[]? _http3EncodedAuthorityHostHeader;
 40
 41        /// <summary>Initially set to null, this can be set to enable HTTP/3 based on Alt-Svc.</summary>
 42        private volatile HttpAuthority? _http3Authority;
 43
 44        /// <summary>A timer to expire <see cref="_http3Authority"/> and return the pool to <see cref="_originAuthority"
 45        private Timer? _authorityExpireTimer;
 46
 47        /// <summary>If true, the <see cref="_http3Authority"/> will persist across a network change. If false, it will 
 48        private bool _persistAuthority;
 49
 50        /// <summary>
 51        /// When an Alt-Svc authority fails due to 421 Misdirected Request, it is placed in the blocklist to be ignored
 52        /// for <see cref="AltSvcBlocklistTimeoutInMilliseconds"/> milliseconds. Initialized on first use.
 53        /// </summary>
 54        private volatile Dictionary<HttpAuthority, Exception?>? _altSvcBlocklist;
 55        private CancellationTokenSource? _altSvcBlocklistTimerCancellation;
 056        private volatile bool _altSvcEnabled = true;
 57
 058        private bool EnableMultipleHttp3Connections => _poolManager.Settings.EnableMultipleHttp3Connections;
 59
 60        // Returns null if HTTP3 cannot be used.
 61        [SupportedOSPlatform("windows")]
 62        [SupportedOSPlatform("linux")]
 63        [SupportedOSPlatform("macos")]
 64        private async ValueTask<HttpResponseMessage?> TrySendUsingHttp3Async(HttpRequestMessage request, CancellationTok
 065        {
 066            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 67
 068            Debug.Assert(_kind == HttpConnectionKind.Https);
 069            Debug.Assert(_http3Enabled);
 70
 71            // Loop in case we get a 421 and need to send the request to a different authority.
 072            while (true)
 073            {
 074                HttpConnectionWaiter<Http3Connection?>? http3ConnectionWaiter = null;
 75                try
 076                {
 077                    if (!TryGetHttp3Authority(request, out HttpAuthority? authority, out Exception? reasonException))
 078                    {
 079                        if (reasonException is null)
 080                        {
 081                            return null;
 82                        }
 083                        ThrowGetVersionException(request, 3, reasonException);
 84                    }
 85
 086                    WaitForHttp3ConnectionActivity waitForConnectionActivity = new WaitForHttp3ConnectionActivity(Settin
 087                    if (!TryGetPooledHttp3Connection(request, out Http3Connection? connection, out http3ConnectionWaiter
 088                    {
 089                        waitForConnectionActivity.Start();
 90                        try
 091                        {
 092                            connection = await http3ConnectionWaiter.WaitWithCancellationAsync(cancellationToken).Config
 093                        }
 094                        catch (Exception ex)
 095                        {
 096                            waitForConnectionActivity.Stop(request, this, ex);
 097                            throw;
 98                        }
 099                    }
 100
 101                    // Request cannot be sent over H/3 connection, try downgrade or report failure.
 102                    // Note that if there's an H/3 suitable origin authority but is unavailable or blocked via Alt-Svc, 
 0103                    if (connection is null)
 0104                    {
 0105                        return null;
 106                    }
 107
 0108                    HttpResponseMessage response = await connection.SendAsync(request, waitForConnectionActivity, stream
 109
 110                    // If an Alt-Svc authority returns 421, it means it can't actually handle the request.
 111                    // An authority is supposed to be able to handle ALL requests to the origin, so this is a server bug
 112                    // In this case, we blocklist the authority and retry the request at the origin.
 0113                    if (response.StatusCode == HttpStatusCode.MisdirectedRequest && connection.Authority != _originAutho
 0114                    {
 0115                        response.Dispose();
 0116                        BlocklistAuthority(connection.Authority);
 0117                        continue;
 118                    }
 119
 0120                    return response;
 121                }
 122                finally
 0123                {
 0124                    http3ConnectionWaiter?.SetTimeoutToPendingConnectionAttempt(this, cancellationToken.IsCancellationRe
 0125                }
 126            }
 0127        }
 128
 129        [SupportedOSPlatform("windows")]
 130        [SupportedOSPlatform("linux")]
 131        [SupportedOSPlatform("macos")]
 132        private bool TryGetPooledHttp3Connection(HttpRequestMessage request, [NotNullWhen(true)] out Http3Connection? co
 0133        {
 0134            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 135
 136            // Look for a usable connection.
 0137            while (true)
 0138            {
 0139                lock (SyncObj)
 0140                {
 0141                    int availableConnectionCount = _availableHttp3Connections?.Count ?? 0;
 0142                    if (availableConnectionCount > 0)
 0143                    {
 144                        // We have a connection that we can attempt to use.
 145                        // Validate it below outside the lock, to avoid doing expensive operations while holding the loc
 0146                        connection = _availableHttp3Connections![availableConnectionCount - 1];
 0147                    }
 148                    else
 0149                    {
 150                        // No available connections. Add to the request queue.
 0151                        waiter = _http3RequestQueue.EnqueueRequest(request);
 152
 0153                        CheckForHttp3ConnectionInjection();
 154
 155                        // There were no available connections. This request has been added to the request queue.
 0156                        if (NetEventSource.Log.IsEnabled()) Trace($"No available HTTP/3 connections; request queued.");
 0157                        connection = null;
 0158                        streamAvailable = false;
 0159                        return false;
 160                    }
 0161                }
 162
 0163                if (CheckExpirationOnGet(connection))
 0164                {
 0165                    if (NetEventSource.Log.IsEnabled()) connection.Trace("Found expired HTTP/3 connection in pool.");
 166
 0167                    InvalidateHttp3Connection(connection);
 0168                    continue;
 169                }
 170
 0171                streamAvailable = connection.TryReserveStream();
 172
 173                // Disable and remove the connection from the pool only if we can open another.
 174                // If we have only single connection, use the underlying QuicConnection mechanism to wait for available 
 0175                if (!streamAvailable && EnableMultipleHttp3Connections)
 0176                {
 0177                    if (NetEventSource.Log.IsEnabled()) connection.Trace("Found HTTP/3 connection in pool without availa
 178
 0179                    bool found = false;
 0180                    lock (SyncObj)
 0181                    {
 0182                        int index = _availableHttp3Connections.IndexOf(connection);
 0183                        if (index != -1)
 0184                        {
 0185                            found = true;
 0186                            _availableHttp3Connections.RemoveAt(index);
 0187                        }
 0188                    }
 189
 190                    // If we didn't find the connection, then someone beat us to removing it (or it shut down)
 0191                    if (found)
 0192                    {
 0193                        DisableHttp3Connection(connection);
 0194                    }
 0195                    continue;
 196                }
 197
 0198                if (NetEventSource.Log.IsEnabled()) connection.Trace("Found usable HTTP/3 connection in pool.");
 0199                waiter = null;
 0200                return true;
 201            }
 0202        }
 203
 204        [SupportedOSPlatform("windows")]
 205        [SupportedOSPlatform("linux")]
 206        [SupportedOSPlatform("macos")]
 207        private void CheckForHttp3ConnectionInjection()
 0208        {
 0209            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 210
 0211            Debug.Assert(HasSyncObjLock);
 212
 0213            _http3RequestQueue.PruneCompletedRequestsFromHeadOfQueue(this);
 214
 215            // Determine if we can and should add a new connection to the pool.
 0216            int availableHttp3ConnectionCount = _availableHttp3Connections?.Count ?? 0;
 0217            bool willInject = availableHttp3ConnectionCount == 0 &&                         // No available connections
 0218                !_pendingHttp3Connection &&                                                 // Only allow one pending HT
 0219                _http3RequestQueue.Count > 0 &&                                             // There are requests left o
 0220                (_associatedHttp3ConnectionCount == 0 || EnableMultipleHttp3Connections) && // We allow multiple connect
 0221                _http3RequestQueue.RequestsWithoutAConnectionAttempt > 0;                   // There are requests we hav
 222
 0223            if (NetEventSource.Log.IsEnabled())
 0224            {
 0225                Trace($"Available HTTP/3.0 connections: {availableHttp3ConnectionCount}, " +
 0226                    $"Pending HTTP/3.0 connection: {_pendingHttp3Connection}, " +
 0227                    $"Requests in the queue: {_http3RequestQueue.Count}, " +
 0228                    $"Requests without a connection attempt: {_http3RequestQueue.RequestsWithoutAConnectionAttempt}, " +
 0229                    $"Total associated HTTP/3.0 connections: {_associatedHttp3ConnectionCount}, " +
 0230                    $"Will inject connection: {willInject}.");
 0231            }
 232
 0233            if (willInject)
 0234            {
 0235                _associatedHttp3ConnectionCount++;
 0236                _pendingHttp3Connection = true;
 237
 0238                RequestQueue<Http3Connection?>.QueueItem queueItem = _http3RequestQueue.PeekNextRequestForConnectionAtte
 0239                _ = InjectNewHttp3ConnectionAsync(queueItem); // ignore returned task
 0240            }
 0241        }
 242
 243        [SupportedOSPlatform("windows")]
 244        [SupportedOSPlatform("linux")]
 245        [SupportedOSPlatform("macos")]
 246        private async Task InjectNewHttp3ConnectionAsync(RequestQueue<Http3Connection?>.QueueItem queueItem)
 0247        {
 0248            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 249
 0250            if (NetEventSource.Log.IsEnabled()) Trace("Creating new HTTP/3 connection for pool.");
 251
 252            // Queue the remainder of the work so that this method completes quickly
 253            // and escapes locks held by the caller.
 0254            await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
 255
 0256            Http3Connection? connection = null;
 0257            Exception? connectionException = null;
 0258            HttpAuthority? authority = null;
 0259            HttpConnectionWaiter<Http3Connection?> waiter = queueItem.Waiter;
 260
 0261            CancellationTokenSource cts = GetConnectTimeoutCancellationTokenSource(waiter);
 0262            Activity? connectionSetupActivity = null;
 263            try
 0264            {
 0265                if (TryGetHttp3Authority(queueItem.Request, out authority, out Exception? reasonException))
 0266                {
 0267                    connectionSetupActivity = ConnectionSetupDistributedTracing.StartConnectionSetupActivity(isSecure: t
 268                    // If the authority was sent as an option through alt-svc then include alt-used header.
 0269                    connection = new Http3Connection(this, authority, includeAltUsedHeader: _http3Authority == authority
 0270                    var connectEndPoint = new DnsEndPoint(authority.IdnHost, authority.Port);
 0271                    QuicConnection quicConnection = await ConnectHelper.ConnectQuicAsync(queueItem.Request, connectEndPo
 0272                    if (quicConnection.NegotiatedApplicationProtocol != SslApplicationProtocol.Http3)
 0273                    {
 0274                        await quicConnection.DisposeAsync().ConfigureAwait(false);
 0275                        throw new HttpRequestException(HttpRequestError.ConnectionError, "QUIC connected but no HTTP/3 i
 276                    }
 0277                    if (connectionSetupActivity is not null) ConnectionSetupDistributedTracing.StopConnectionSetupActivi
 0278                    connection.InitQuicConnection(quicConnection, connectionSetupActivity, connectEndPoint);
 0279                }
 0280                else if (reasonException is not null)
 0281                {
 0282                    ThrowGetVersionException(queueItem.Request, 3, reasonException);
 283                }
 0284            }
 0285            catch (Exception e)
 0286            {
 0287                connectionException = e is OperationCanceledException oce && oce.CancellationToken == cts.Token && !wait
 0288                    CreateConnectTimeoutException(oce) :
 0289                    e;
 290
 291                // On success path connectionSetupActivity is stopped before calling InitQuicConnection().
 292                // This assertion makes sure that InitQuicConnection() does not throw unexpectedly.
 0293                Debug.Assert(connectionSetupActivity?.IsStopped is not true);
 0294                if (connectionSetupActivity is not null) ConnectionSetupDistributedTracing.StopConnectionSetupActivity(c
 295
 296                // If the connection hasn't been initialized with QuicConnection, get rid of it.
 0297                connection?.Dispose();
 0298                connection = null;
 0299            }
 300            finally
 0301            {
 0302                lock (waiter)
 0303                {
 0304                    waiter.ConnectionCancellationTokenSource = null;
 0305                    cts.Dispose();
 0306                }
 0307            }
 308
 0309            if (connection is not null)
 0310            {
 311                // Add the new connection to the pool.
 0312                ReturnHttp3Connection(connection, isNewConnection: true, waiter);
 0313            }
 314            else
 0315            {
 316                // Block list authority only if the connection attempt was not cancelled.
 0317                if (connectionException is not null && connectionException is not OperationCanceledException && authorit
 0318                {
 319                    // Disables HTTP/3 until server announces it can handle it via Alt-Svc.
 0320                    BlocklistAuthority(authority, connectionException);
 0321                }
 322
 0323                HandleHttp3ConnectionFailure(waiter, connectionException);
 0324            }
 0325        }
 326
 327        [SupportedOSPlatform("windows")]
 328        [SupportedOSPlatform("linux")]
 329        [SupportedOSPlatform("macos")]
 330        private void HandleHttp3ConnectionFailure(HttpConnectionWaiter<Http3Connection?> requestWaiter, Exception? e)
 0331        {
 0332            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 333
 0334            if (NetEventSource.Log.IsEnabled()) Trace($"HTTP3 connection failed: {e}");
 335
 336            // We don't care if this fails; that means the request was previously canceled or handled by a different con
 0337            if (e is null)
 0338            {
 0339                requestWaiter.TrySetResult(null);
 0340            }
 341            else
 0342            {
 0343                requestWaiter.TrySetException(e);
 0344            }
 345
 0346            lock (SyncObj)
 0347            {
 0348                Debug.Assert(_associatedHttp3ConnectionCount > 0);
 0349                Debug.Assert(_pendingHttp3Connection);
 350
 0351                _associatedHttp3ConnectionCount--;
 0352                _pendingHttp3Connection = false;
 353
 0354                CheckForHttp3ConnectionInjection();
 0355            }
 0356        }
 357
 358        [SupportedOSPlatform("windows")]
 359        [SupportedOSPlatform("linux")]
 360        [SupportedOSPlatform("macos")]
 361        private void ReturnHttp3Connection(Http3Connection connection, bool isNewConnection, HttpConnectionWaiter<Http3C
 0362        {
 0363            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 364
 0365            if (NetEventSource.Log.IsEnabled()) connection.Trace($"{nameof(isNewConnection)}={isNewConnection}");
 366
 0367            Debug.Assert(!HasSyncObjLock);
 0368            Debug.Assert(isNewConnection || initialRequestWaiter is null, "Shouldn't have a request unless the connectio
 369
 0370            if (!isNewConnection && CheckExpirationOnReturn(connection))
 0371            {
 0372                lock (SyncObj)
 0373                {
 0374                    Debug.Assert(_availableHttp3Connections is null || !_availableHttp3Connections.Contains(connection))
 0375                    Debug.Assert(_associatedHttp3ConnectionCount > (_availableHttp3Connections?.Count ?? 0));
 0376                    _associatedHttp3ConnectionCount--;
 0377                }
 378
 0379                if (NetEventSource.Log.IsEnabled()) connection.Trace("Disposing HTTP3 connection return to pool. Connect
 0380                connection.Dispose();
 0381                return;
 382            }
 383
 0384            while (connection.TryReserveStream() || !EnableMultipleHttp3Connections)
 0385            {
 386                // Loop in case we get a request that has already been canceled or handled by a different connection.
 0387                while (true)
 0388                {
 0389                    HttpConnectionWaiter<Http3Connection?>? waiter = null;
 0390                    bool added = false;
 0391                    lock (SyncObj)
 0392                    {
 0393                        Debug.Assert(_availableHttp3Connections is null || !_availableHttp3Connections.Contains(connecti
 0394                        Debug.Assert(_associatedHttp3ConnectionCount > (_availableHttp3Connections?.Count ?? 0),
 0395                            $"Expected _associatedHttp3ConnectionCount={_associatedHttp3ConnectionCount} > _availableHtt
 396
 0397                        if (isNewConnection)
 0398                        {
 0399                            Debug.Assert(_pendingHttp3Connection);
 0400                            _pendingHttp3Connection = false;
 0401                            isNewConnection = false;
 0402                        }
 403
 0404                        if (initialRequestWaiter is not null)
 0405                        {
 406                            // Try to handle the request that we initiated the connection for first
 0407                            waiter = initialRequestWaiter;
 0408                            initialRequestWaiter = null;
 409
 410                            // If this method found a request to service, that request must be removed from the queue if
 411                            // Normally, TryDequeueWaiter would handle the removal. TryDequeueSpecificWaiter matches thi
 412                            // We don't care if this fails; that means the request was previously canceled, handled by a
 0413                            _http3RequestQueue.TryDequeueSpecificWaiter(waiter);
 0414                        }
 0415                        else if (_http3RequestQueue.TryDequeueWaiter(this, out waiter))
 0416                        {
 0417                            Debug.Assert((_availableHttp3Connections?.Count ?? 0) == 0, $"With {(_availableHttp3Connecti
 0418                        }
 0419                        else if (_disposed)
 0420                        {
 421                            // The pool has been disposed. We will dispose this connection below outside the lock.
 422                            // We do this check after processing the request queue so that any queued requests will be h
 0423                            _associatedHttp3ConnectionCount--;
 0424                        }
 425                        else
 0426                        {
 427                            // Add connection to the pool.
 0428                            added = true;
 0429                            _availableHttp3Connections ??= new List<Http3Connection>();
 0430                            _availableHttp3Connections.Add(connection);
 0431                        }
 0432                    }
 433
 0434                    if (waiter is not null)
 0435                    {
 0436                        Debug.Assert(!added);
 437
 0438                        if (waiter.TrySignal(connection))
 0439                        {
 0440                            break;
 441                        }
 442
 443                        // Loop and process the queue again
 0444                    }
 445                    else
 0446                    {
 447                        // TryReserveStream() always decrements the available stream counter when EnableMultipleHttp3Con
 0448                        connection.ReleaseStream();
 449
 0450                        if (added)
 0451                        {
 0452                            if (NetEventSource.Log.IsEnabled()) connection.Trace("Put HTTP3 connection in pool.");
 0453                            return;
 454                        }
 455                        else
 0456                        {
 0457                            Debug.Assert(_disposed);
 0458                            if (NetEventSource.Log.IsEnabled()) connection.Trace("Disposing HTTP3 connection returned to
 0459                            connection.Dispose();
 0460                            return;
 461                        }
 462                    }
 0463                }
 0464            }
 465
 466            // Since we only inject one connection at a time, we may want to inject another now.
 0467            lock (SyncObj)
 0468            {
 0469                CheckForHttp3ConnectionInjection();
 0470            }
 471
 472            // We need to wait until the connection is usable again.
 0473            DisableHttp3Connection(connection);
 0474        }
 475
 476        /// <summary>
 477        /// Disable usage of the specified connection because it cannot handle any more streams at the moment.
 478        /// We will register to be notified when it can handle more streams (or becomes permanently unusable).
 479        /// </summary>
 480        [SupportedOSPlatform("windows")]
 481        [SupportedOSPlatform("linux")]
 482        [SupportedOSPlatform("macos")]
 483        private void DisableHttp3Connection(Http3Connection connection)
 0484        {
 0485            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 486
 0487            if (NetEventSource.Log.IsEnabled()) connection.Trace("");
 488
 0489            _ = DisableHttp3ConnectionAsync(connection); // ignore returned task
 490
 491            async Task DisableHttp3ConnectionAsync(Http3Connection connection)
 0492            {
 0493                bool usable = await connection.WaitForAvailableStreamsAsync().ConfigureAwait(ConfigureAwaitOptions.Force
 494
 0495                if (NetEventSource.Log.IsEnabled()) connection.Trace($"{nameof(connection.WaitForAvailableStreamsAsync)}
 496
 0497                if (usable)
 0498                {
 0499                    ReturnHttp3Connection(connection, isNewConnection: false);
 0500                }
 501                else
 0502                {
 503                    // Connection has shut down.
 0504                    lock (SyncObj)
 0505                    {
 0506                        Debug.Assert(_availableHttp3Connections is null || !_availableHttp3Connections.Contains(connecti
 0507                        Debug.Assert(_associatedHttp3ConnectionCount > 0);
 508
 0509                        _associatedHttp3ConnectionCount--;
 510
 0511                        CheckForHttp3ConnectionInjection();
 0512                    }
 513
 0514                    if (NetEventSource.Log.IsEnabled()) connection.Trace("HTTP3 connection no longer usable");
 0515                    connection.Dispose();
 0516                }
 0517            }
 0518        }
 519
 520        /// <summary>
 521        /// Called when an Http3Connection from this pool is no longer usable.
 522        /// </summary>
 523        [SupportedOSPlatform("windows")]
 524        [SupportedOSPlatform("linux")]
 525        [SupportedOSPlatform("macos")]
 526        public void InvalidateHttp3Connection(Http3Connection connection, bool dispose = true)
 0527        {
 0528            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 529
 0530            if (NetEventSource.Log.IsEnabled()) connection.Trace("");
 531
 0532            bool found = false;
 0533            lock (SyncObj)
 0534            {
 0535                if (_availableHttp3Connections is not null)
 0536                {
 0537                    Debug.Assert(_associatedHttp3ConnectionCount >= _availableHttp3Connections.Count);
 538
 0539                    int index = _availableHttp3Connections.IndexOf(connection);
 0540                    if (index != -1)
 0541                    {
 0542                        found = true;
 0543                        _availableHttp3Connections.RemoveAt(index);
 0544                        _associatedHttp3ConnectionCount--;
 0545                    }
 0546                }
 547
 0548                CheckForHttp3ConnectionInjection();
 0549            }
 550
 551            // If we found the connection in the available list, then dispose it now.
 552            // Otherwise, when we try to put it back in the pool, we will see it is shut down and dispose it (and adjust
 0553            if (found && dispose)
 0554            {
 0555                connection.Dispose();
 0556            }
 0557        }
 558
 559        [SupportedOSPlatform("windows")]
 560        [SupportedOSPlatform("linux")]
 561        [SupportedOSPlatform("macos")]
 562        private static int ScavengeHttp3ConnectionList(List<Http3Connection> list, ref List<HttpConnectionBase>? toDispo
 0563        {
 0564            Debug.Assert(GlobalHttpSettings.SocketsHttpHandler.AllowHttp3);
 565
 0566            int freeIndex = 0;
 0567            while (freeIndex < list.Count && list[freeIndex].IsUsable(nowTicks, pooledConnectionLifetime, pooledConnecti
 0568            {
 0569                freeIndex++;
 0570            }
 571
 572            // If freeIndex == list.Count, nothing needs to be removed.
 573            // But if it's < list.Count, at least one connection needs to be purged.
 0574            int removed = 0;
 0575            if (freeIndex < list.Count)
 0576            {
 577                // We know the connection at freeIndex is unusable, so dispose of it.
 0578                toDispose ??= new List<HttpConnectionBase>();
 0579                toDispose.Add(list[freeIndex]);
 580
 581                // Find the first item after the one to be removed that should be kept.
 0582                int current = freeIndex + 1;
 0583                while (current < list.Count)
 0584                {
 585                    // Look for the first item to be kept.  Along the way, any
 586                    // that shouldn't be kept are disposed of.
 0587                    while (current < list.Count && !list[current].IsUsable(nowTicks, pooledConnectionLifetime, pooledCon
 0588                    {
 0589                        toDispose.Add(list[current]);
 0590                        current++;
 0591                    }
 592
 593                    // If we found something to keep, copy it down to the known free slot.
 0594                    if (current < list.Count)
 0595                    {
 596                        // copy item to the free slot
 0597                        list[freeIndex++] = list[current++];
 0598                    }
 599
 600                    // Keep going until there are no more good items.
 0601                }
 602
 603                // At this point, good connections have been moved below freeIndex, and garbage connections have
 604                // been added to the dispose list, so clear the end of the list past freeIndex.
 0605                removed = list.Count - freeIndex;
 0606                list.RemoveRange(freeIndex, removed);
 0607            }
 608
 0609            return removed;
 0610        }
 611
 612        private bool TryGetHttp3Authority(HttpRequestMessage request, [NotNullWhen(true)] out HttpAuthority? authority, 
 0613        {
 0614            authority = _http3Authority;
 615
 616            // If H3 is explicitly requested, assume pre-negotiated H3.
 0617            if (request.Version.Major >= 3 && request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
 0618            {
 0619                authority ??= _originAuthority;
 0620            }
 621
 0622            if (authority is null)
 0623            {
 0624                reasonException = null;
 0625                return false;
 626            }
 627
 0628            if (IsAltSvcBlocked(authority, out reasonException))
 0629            {
 0630                return false;
 631            }
 632
 0633            return true;
 0634        }
 635
 636
 637        /// <summary>Check for the Alt-Svc header, to upgrade to HTTP/3.</summary>
 638        private void ProcessAltSvc(HttpResponseMessage response)
 0639        {
 0640            if (_altSvcEnabled && response.Headers.TryGetValues(KnownHeaders.AltSvc.Descriptor, out IEnumerable<string>?
 0641            {
 0642                HandleAltSvc(altSvcHeaderValues, response.Headers.Age);
 0643            }
 0644        }
 645
 646        /// <summary>
 647        /// Inspects a collection of Alt-Svc headers to find the first eligible upgrade path.
 648        /// </summary>
 649        /// <remarks>TODO: common case will likely be a single value. Optimize for that.</remarks>
 650        internal void HandleAltSvc(IEnumerable<string> altSvcHeaderValues, TimeSpan? responseAge)
 0651        {
 0652            HttpAuthority? nextAuthority = null;
 0653            TimeSpan nextAuthorityMaxAge = default;
 0654            bool nextAuthorityPersist = false;
 655
 0656            foreach (string altSvcHeaderValue in altSvcHeaderValues)
 0657            {
 0658                int parseIdx = 0;
 659
 0660                if (AltSvcHeaderParser.Parser.TryParseValue(altSvcHeaderValue, null, ref parseIdx, out object? parsedVal
 0661                {
 0662                    Debug.Assert(parsedValue is not null);
 663
 0664                    var value = (AltSvcHeaderValue)parsedValue;
 665
 666                    // 'clear' should be the only value present.
 0667                    if (ReferenceEquals(AltSvcHeaderValue.Clear, value))
 0668                    {
 0669                        lock (SyncObj)
 0670                        {
 671                            // Clear invalidates all Alt-Svc including the current response ones.
 672                            // https://httpwg.org/specs/rfc7838.html#alt-svc
 0673                            ExpireAltSvcAuthority();
 0674                            Debug.Assert(_authorityExpireTimer != null || _disposed);
 0675                            _authorityExpireTimer?.Change(Timeout.Infinite, Timeout.Infinite);
 0676                            return;
 677                        }
 678                    }
 679
 680                    // Do not process the Alt-Svc header if we've already found a valid h3 authority before, but continu
 0681                    if (nextAuthority is not null || value.AlpnProtocolName != "h3")
 0682                    {
 0683                        continue;
 684                    }
 685
 0686                    var authority = new HttpAuthority(value.Host ?? _originAuthority.IdnHost, value.Port);
 0687                    if (IsAltSvcBlocked(authority, out _))
 0688                    {
 689                        // Skip authorities in our blocklist.
 0690                        continue;
 691                    }
 692
 0693                    TimeSpan authorityMaxAge = value.MaxAge;
 694
 0695                    if (responseAge != null)
 0696                    {
 0697                        authorityMaxAge -= responseAge.GetValueOrDefault();
 0698                    }
 699
 700                    // It's already out of date, skip it.
 0701                    if (authorityMaxAge <= TimeSpan.Zero)
 0702                    {
 0703                        continue;
 704                    }
 705
 706                    // We found an h3 authority that is not blocked and has not aged out.
 0707                    nextAuthority = authority;
 0708                    nextAuthorityMaxAge = authorityMaxAge;
 0709                    nextAuthorityPersist = value.Persist;
 0710                }
 0711            }
 712
 713            // There's a race here in checking _http3Authority outside of the lock,
 714            // but there's really no bad behavior if _http3Authority changes in the mean time.
 0715            if (nextAuthority != null && !nextAuthority.Equals(_http3Authority))
 0716            {
 717                // Clamp the max age to 30 days... this is arbitrary but prevents passing a too-large TimeSpan to the Ti
 0718                if (nextAuthorityMaxAge.Ticks > (30 * TimeSpan.TicksPerDay))
 0719                {
 0720                    nextAuthorityMaxAge = TimeSpan.FromTicks(30 * TimeSpan.TicksPerDay);
 0721                }
 722
 0723                lock (SyncObj)
 0724                {
 0725                    if (_disposed)
 0726                    {
 727                        // avoid creating or touching _authorityExpireTimer after disposal
 0728                        return;
 729                    }
 730
 0731                    if (_authorityExpireTimer == null)
 0732                    {
 0733                        var thisRef = new WeakReference<HttpConnectionPool>(this);
 734
 0735                        using (ExecutionContext.SuppressFlow())
 0736                        {
 0737                            _authorityExpireTimer = new Timer(static o =>
 0738                            {
 0739                                var wr = (WeakReference<HttpConnectionPool>)o!;
 0740                                if (wr.TryGetTarget(out HttpConnectionPool? @this))
 0741                                {
 0742                                    lock (@this.SyncObj)
 0743                                    {
 0744                                        @this.ExpireAltSvcAuthority();
 0745                                    }
 0746                                }
 0747                            }, thisRef, nextAuthorityMaxAge, Timeout.InfiniteTimeSpan);
 0748                        }
 0749                    }
 750                    else
 0751                    {
 0752                        _authorityExpireTimer.Change(nextAuthorityMaxAge, Timeout.InfiniteTimeSpan);
 0753                    }
 754
 0755                    _http3Authority = nextAuthority;
 0756                    _persistAuthority = nextAuthorityPersist;
 0757                }
 758
 759                // NetworkChange notifications aren't supported on every platform.
 0760                if (!nextAuthorityPersist && NetworkChange.IsSupported)
 0761                {
 0762                    _poolManager.StartMonitoringNetworkChanges();
 0763                }
 0764            }
 0765        }
 766
 767        /// <summary>
 768        /// Expires the current Alt-Svc authority, resetting the connection back to origin.
 769        /// </summary>
 770        private void ExpireAltSvcAuthority()
 0771        {
 0772            Debug.Assert(HasSyncObjLock);
 773
 774            // If we ever support prenegotiated HTTP/3, this should be set to origin, not nulled out.
 0775            _http3Authority = null;
 0776        }
 777
 778        /// <summary>
 779        /// Checks whether the given <paramref name="authority"/> is on the currext Alt-Svc blocklist.
 780        /// If it is, then it places the cause in the <paramref name="reasonException"/>
 781        /// </summary>
 782        /// <seealso cref="BlocklistAuthority" />
 783        private bool IsAltSvcBlocked(HttpAuthority authority, out Exception? reasonException)
 0784        {
 0785            if (_altSvcBlocklist != null)
 0786            {
 0787                lock (_altSvcBlocklist)
 0788                {
 0789                    return _altSvcBlocklist.TryGetValue(authority, out reasonException);
 790                }
 791            }
 0792            reasonException = null;
 0793            return false;
 0794        }
 795
 796        /// <summary>
 797        /// Blocklists an authority and resets the current authority back to origin.
 798        /// If the number of blocklisted authorities exceeds <see cref="MaxAltSvcIgnoreListSize"/>,
 799        /// Alt-Svc will be disabled entirely for a period of time.
 800        /// </summary>
 801        /// <remarks>
 802        /// This is called when we get a "421 Misdirected Request" from an alternate authority.
 803        /// A future strategy would be to retry the individual request on an older protocol, we'd want to have
 804        /// some logic to blocklist after some number of failures to avoid doubling our request latency.
 805        ///
 806        /// For now, the spec states alternate authorities should be able to handle ALL requests, so this
 807        /// is treated as an exceptional error by immediately blocklisting the authority.
 808        /// </remarks>
 809        internal void BlocklistAuthority(HttpAuthority badAuthority, Exception? exception = null)
 0810        {
 0811            Debug.Assert(badAuthority != null);
 812
 0813            Dictionary<HttpAuthority, Exception?>? altSvcBlocklist = _altSvcBlocklist;
 814
 0815            if (altSvcBlocklist == null)
 0816            {
 0817                lock (SyncObj)
 0818                {
 0819                    if (_disposed)
 0820                    {
 821                        // avoid creating _altSvcBlocklistTimerCancellation after disposal
 0822                        return;
 823                    }
 824
 0825                    altSvcBlocklist = _altSvcBlocklist;
 0826                    if (altSvcBlocklist == null)
 0827                    {
 0828                        altSvcBlocklist = new Dictionary<HttpAuthority, Exception?>();
 0829                        _altSvcBlocklistTimerCancellation = new CancellationTokenSource();
 0830                        _altSvcBlocklist = altSvcBlocklist;
 0831                    }
 0832                }
 0833            }
 834
 0835            bool added, disabled = false;
 836
 0837            lock (altSvcBlocklist)
 0838            {
 0839                added = altSvcBlocklist.TryAdd(badAuthority, exception);
 840
 0841                if (added && altSvcBlocklist.Count >= MaxAltSvcIgnoreListSize && _altSvcEnabled)
 0842                {
 0843                    _altSvcEnabled = false;
 0844                    disabled = true;
 0845                }
 0846            }
 847
 848            CancellationToken altSvcBlocklistTimerCt;
 849
 0850            lock (SyncObj)
 0851            {
 0852                if (_disposed)
 0853                {
 854                    // avoid touching _authorityExpireTimer and _altSvcBlocklistTimerCancellation after disposal
 0855                    return;
 856                }
 857
 0858                if (_http3Authority == badAuthority)
 0859                {
 0860                    ExpireAltSvcAuthority();
 0861                    Debug.Assert(_authorityExpireTimer != null);
 0862                    _authorityExpireTimer.Change(Timeout.Infinite, Timeout.Infinite);
 0863                }
 864
 0865                Debug.Assert(_altSvcBlocklistTimerCancellation != null);
 0866                altSvcBlocklistTimerCt = _altSvcBlocklistTimerCancellation.Token;
 0867            }
 868
 0869            if (added)
 0870            {
 0871                _ = Task.Delay(AltSvcBlocklistTimeoutInMilliseconds, altSvcBlocklistTimerCt)
 0872                    .ContinueWith(t =>
 0873                    {
 0874                        lock (altSvcBlocklist)
 0875                        {
 0876                            altSvcBlocklist.Remove(badAuthority);
 0877                        }
 0878                    }, altSvcBlocklistTimerCt, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
 0879            }
 880
 0881            if (disabled)
 0882            {
 0883                _ = Task.Delay(AltSvcBlocklistTimeoutInMilliseconds, altSvcBlocklistTimerCt)
 0884                    .ContinueWith(t =>
 0885                    {
 0886                        _altSvcEnabled = true;
 0887                    }, altSvcBlocklistTimerCt, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
 0888            }
 0889        }
 890
 891        public void OnNetworkChanged()
 0892        {
 0893            lock (SyncObj)
 0894            {
 0895                if (_http3Authority != null && !_persistAuthority)
 0896                {
 0897                    ExpireAltSvcAuthority();
 0898                    Debug.Assert(_authorityExpireTimer != null || _disposed);
 0899                    _authorityExpireTimer?.Change(Timeout.Infinite, Timeout.Infinite);
 0900                }
 0901            }
 0902        }
 903    }
 904}
 905

Methods/Properties

.cctor()
.ctor(System.Net.Http.HttpConnectionPoolManager,System.Net.Http.HttpConnectionKind,System.String,System.Int32,System.String,System.Uri,System.String)
ConstructSslOptions(System.Net.Http.HttpConnectionPoolManager,System.String)
TelemetryServerAddress()
OriginAuthority()
Settings()
Kind()
IsSecure()
ProxyUri()
ProxyCredentials()
PreAuthCredentials()
IsDefaultPort()
DoProxyAuth()
SyncObj()
HasSyncObjLock()
SendAsync(System.Net.Http.HttpRequestMessage,System.Boolean,System.Boolean,System.Threading.CancellationToken)
SendWithProxyAuthAsync(System.Net.Http.HttpRequestMessage,System.Boolean,System.Boolean,System.Threading.CancellationToken)
SendWithNtConnectionAuthAsync(System.Net.Http.HttpConnection,System.Net.Http.HttpRequestMessage,System.Boolean,System.Boolean,System.Threading.CancellationToken)
SendWithNtProxyAuthAsync(System.Net.Http.HttpConnection,System.Net.Http.HttpRequestMessage,System.Boolean,System.Threading.CancellationToken)
SendWithVersionDetectionAndRetryAsync(System.Net.Http.HttpRequestMessage,System.Boolean,System.Boolean,System.Threading.CancellationToken)
ConnectAsync(System.Net.Http.HttpRequestMessage,System.Boolean,System.Boolean,System.Threading.CancellationToken)
GetRemoteEndPoint(System.IO.Stream)
ConnectToTcpHostAsync(System.String,System.Int32,System.Net.Http.HttpRequestMessage,System.Boolean,System.Int64,System.Threading.CancellationToken)
GetSslOptionsForRequest(System.Net.Http.HttpRequestMessage,System.Boolean)
ApplyPlaintextFilterAsync(System.Boolean,System.IO.Stream,System.Version,System.Net.Http.HttpRequestMessage,System.Int64,System.Threading.CancellationToken)
EstablishProxyTunnelAsync(System.Boolean,System.Threading.CancellationToken)
EstablishSocksTunnel(System.Net.Http.HttpRequestMessage,System.Boolean,System.Int64,System.Threading.CancellationToken)
GetConnectTimeoutCancellationTokenSource(System.Net.Http.HttpConnectionWaiter`1<T>)
CreateConnectTimeoutException(System.OperationCanceledException)
ThrowGetVersionException(System.Net.Http.HttpRequestMessage,System.Int32,System.Exception)
CanFallBackToHttp11(System.Net.Http.HttpRequestMessage)
CheckExpirationOnGet(System.Net.Http.HttpConnectionBase)
CheckExpirationOnReturn(System.Net.Http.HttpConnectionBase)
EvictionGeneration()
EvaluateConnectionsForEviction()
Dispose()
CleanCacheAndDisposeIfUnused()
ToString()
Trace(System.String,System.String)
.ctor(System.Net.Http.HttpConnectionPoolManager,System.Net.Http.HttpConnectionKind,System.String,System.Int32,System.String,System.Uri,System.String)
HostHeaderLineBytes()
TryGetPooledHttp11Connection(System.Net.Http.HttpRequestMessage,System.Boolean,System.Net.Http.HttpConnection&,System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.HttpConnection>&)
ProcessHttp11RequestQueue(System.Net.Http.HttpConnection)
CheckForHttp11ConnectionInjection()
InjectNewHttp11ConnectionAsync(System.Net.Http.RequestQueue`1/QueueItem<System.Net.Http.HttpConnection>)
CreateHttp11ConnectionAsync(System.Net.Http.HttpRequestMessage,System.Boolean,System.Threading.CancellationToken)
ConstructHttp11ConnectionAsync(System.Boolean,System.IO.Stream,System.Net.TransportContext,System.Net.Http.HttpRequestMessage,System.Diagnostics.Activity,System.Net.IPEndPoint,System.Int64,System.Threading.CancellationToken)
HandleHttp11ConnectionFailure(System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.HttpConnection>,System.Exception)
RecycleHttp11Connection(System.Net.Http.HttpConnection)
AddNewHttp11Connection(System.Net.Http.HttpConnection,System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.HttpConnection>)
ReturnHttp11Connection(System.Net.Http.HttpConnection)
InvalidateHttp11Connection(System.Net.Http.HttpConnection,System.Boolean)
ScavengeHttp11ConnectionStack(System.Net.Http.HttpConnectionPool,System.Collections.Concurrent.ConcurrentStack`1<System.Net.Http.HttpConnection>,System.Collections.Generic.List`1<System.Net.Http.HttpConnectionBase>&,System.Int64,System.TimeSpan,System.TimeSpan)
Http2AltSvcOriginUri()
EnableMultipleHttp2Connections()
TryGetPooledHttp2Connection(System.Net.Http.HttpRequestMessage,System.Net.Http.Http2Connection&,System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.Http2Connection>&)
CheckForHttp2ConnectionInjection()
InjectNewHttp2ConnectionAsync(System.Net.Http.RequestQueue`1/QueueItem<System.Net.Http.Http2Connection>)
ConstructHttp2ConnectionAsync(System.IO.Stream,System.Net.Http.HttpRequestMessage,System.Diagnostics.Activity,System.Net.IPEndPoint,System.Int64,System.Threading.CancellationToken)
HandleHttp2ConnectionFailure(System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.Http2Connection>,System.Exception)
OnSessionAuthenticationChallengeSeen()
HandleHttp11Downgrade(System.Net.Http.HttpRequestMessage,System.IO.Stream,System.Net.TransportContext,System.Diagnostics.Activity,System.Net.IPEndPoint,System.Int64,System.Threading.CancellationToken)
ReturnHttp2Connection(System.Net.Http.Http2Connection,System.Boolean,System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.Http2Connection>)
DisableHttp2Connection(System.Net.Http.Http2Connection)
DisableHttp2ConnectionAsync(System.Net.Http.Http2Connection)
InvalidateHttp2Connection(System.Net.Http.Http2Connection)
Http2KeepAlivePingEnabled()
AddHttp2ConnectionForHeartBeat(System.Net.Http.Http2Connection)
RemoveHttp2ConnectionFromHeartBeat(System.Net.Http.Http2Connection)
HeartBeat()
ScavengeHttp2ConnectionList(System.Collections.Generic.List`1<System.Net.Http.Http2Connection>,System.Collections.Generic.List`1<System.Net.Http.HttpConnectionBase>&,System.Int64,System.TimeSpan,System.TimeSpan)
.ctor(System.Net.Http.HttpConnectionPoolManager,System.Net.Http.HttpConnectionKind,System.String,System.Int32,System.String,System.Uri,System.String)
EnableMultipleHttp3Connections()
TrySendUsingHttp3Async(System.Net.Http.HttpRequestMessage,System.Threading.CancellationToken)
TryGetPooledHttp3Connection(System.Net.Http.HttpRequestMessage,System.Net.Http.Http3Connection&,System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.Http3Connection>&,System.Boolean&)
CheckForHttp3ConnectionInjection()
InjectNewHttp3ConnectionAsync(System.Net.Http.RequestQueue`1/QueueItem<System.Net.Http.Http3Connection>)
HandleHttp3ConnectionFailure(System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.Http3Connection>,System.Exception)
ReturnHttp3Connection(System.Net.Http.Http3Connection,System.Boolean,System.Net.Http.HttpConnectionWaiter`1<System.Net.Http.Http3Connection>)
DisableHttp3Connection(System.Net.Http.Http3Connection)
DisableHttp3ConnectionAsync(System.Net.Http.Http3Connection)
InvalidateHttp3Connection(System.Net.Http.Http3Connection,System.Boolean)
ScavengeHttp3ConnectionList(System.Collections.Generic.List`1<System.Net.Http.Http3Connection>,System.Collections.Generic.List`1<System.Net.Http.HttpConnectionBase>&,System.Int64,System.TimeSpan,System.TimeSpan)
TryGetHttp3Authority(System.Net.Http.HttpRequestMessage,System.Net.Http.HttpAuthority&,System.Exception&)
ProcessAltSvc(System.Net.Http.HttpResponseMessage)
HandleAltSvc(System.Collections.Generic.IEnumerable`1<System.String>,System.Nullable`1<System.TimeSpan>)
ExpireAltSvcAuthority()
IsAltSvcBlocked(System.Net.Http.HttpAuthority,System.Exception&)
BlocklistAuthority(System.Net.Http.HttpAuthority,System.Exception)
OnNetworkChanged()