public ValueTask <HttpConnection> GetConnectionAsync <TState>(Func <TState, ValueTask <HttpConnection> > createConnection, TState state) { List <CachedConnection> list = _idleConnections; lock (SyncObj) { // Try to return a cached connection. We need to loop in case the connection // we get from the list is unusable. while (list.Count > 0) { CachedConnection cachedConnection = list[list.Count - 1]; list.RemoveAt(list.Count - 1); if (cachedConnection.IsUsable()) { // We found a valid collection. Return it. return(new ValueTask <HttpConnection>(cachedConnection._connection)); } // We got a connection, but it was already closed by the server or the // server sent unexpected data or the connection is too old. In any case, // we can't use the connection, so get rid of it and try again. cachedConnection._connection.Dispose(); } // No valid cached connections, so we need to create a new one. If // there's no limit on the number of connections associated with this // pool, or if we haven't reached such a limit, simply create a new // connection. if (_waiters == null || _associatedConnectionCount < _maxConnections) { IncrementConnectionCountNoLock(); return(WaitForCreatedConnectionAsync(createConnection(state))); } else { // There is a limit, and we've reached it, which means we need to // wait for a connection to be returned to the pool or for a connection // associated with the pool to be dropped before we can create a // new one. Create a waiter object and register it with the pool; it'll // be signaled with the created connection when one is returned or // space is available and the provided creation func has successfully // created the connection to be used. var waiter = new ConnectionWaiter <TState>(this, createConnection, state); _waiters.Enqueue(waiter); return(new ValueTask <HttpConnection>(waiter.Task)); } // Note that we don't check for _disposed. We may end up disposing the // created connection when it's returned, but we don't want to block use // of the pool if it's already been disposed, as there's a race condition // between getting a pool and someone disposing of it, and we don't want // to complicate the logic about trying to get a different pool when the // retrieved one has been disposed of. In the future we could alternatively // try returning such connections to whatever pool is currently considered // current for that endpoint, if there is one. } }
public ValueTask <HttpConnection> GetConnectionAsync <TState>( Func <TState, CancellationToken, ValueTask <HttpConnection> > createConnection, TState state, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(new ValueTask <HttpConnection>(Task.FromCanceled <HttpConnection>(cancellationToken))); } List <CachedConnection> list = _idleConnections; lock (SyncObj) { // Try to return a cached connection. We need to loop in case the connection // we get from the list is unusable. while (list.Count > 0) { CachedConnection cachedConnection = list[list.Count - 1]; HttpConnection conn = cachedConnection._connection; list.RemoveAt(list.Count - 1); if (cachedConnection.IsUsable()) { // We found a valid collection. Return it. if (NetEventSource.IsEnabled) { conn.Trace("Found usable connection in pool."); } return(new ValueTask <HttpConnection>(conn)); } // We got a connection, but it was already closed by the server or the // server sent unexpected data or the connection is too old. In any case, // we can't use the connection, so get rid of it and try again. if (NetEventSource.IsEnabled) { conn.Trace("Found invalid connection in pool."); } conn.Dispose(); } // No valid cached connections, so we need to create a new one. If // there's no limit on the number of connections associated with this // pool, or if we haven't reached such a limit, simply create a new // connection. if (_associatedConnectionCount < _maxConnections) { if (NetEventSource.IsEnabled) { Trace("Creating new connection for pool."); } IncrementConnectionCountNoLock(); return(WaitForCreatedConnectionAsync(createConnection(state, cancellationToken))); } else { // There is a limit, and we've reached it, which means we need to // wait for a connection to be returned to the pool or for a connection // associated with the pool to be dropped before we can create a // new one. Create a waiter object and register it with the pool; it'll // be signaled with the created connection when one is returned or // space is available and the provided creation func has successfully // created the connection to be used. if (NetEventSource.IsEnabled) { Trace("Limit reached. Waiting to create new connection."); } var waiter = new ConnectionWaiter <TState>(this, createConnection, state, cancellationToken); EnqueueWaiter(waiter); if (cancellationToken.CanBeCanceled) { // If cancellation could be requested, register a callback for it that'll cancel // the waiter and remove the waiter from the queue. Note that this registration needs // to happen under the reentrant lock and after enqueueing the waiter. waiter._cancellationTokenRegistration = cancellationToken.Register(s => { var innerWaiter = (ConnectionWaiter)s; lock (innerWaiter._pool.SyncObj) { // If it's in the list, remove it and cancel it. if (innerWaiter._pool.RemoveWaiterForCancellation(innerWaiter)) { bool canceled = innerWaiter.TrySetCanceled(innerWaiter._cancellationToken); Debug.Assert(canceled); } } }, waiter); } return(new ValueTask <HttpConnection>(waiter.Task)); } // Note that we don't check for _disposed. We may end up disposing the // created connection when it's returned, but we don't want to block use // of the pool if it's already been disposed, as there's a race condition // between getting a pool and someone disposing of it, and we don't want // to complicate the logic about trying to get a different pool when the // retrieved one has been disposed of. In the future we could alternatively // try returning such connections to whatever pool is currently considered // current for that endpoint, if there is one. } }