コード例 #1
0
        /// <summary>Removes the specified waiter from the waiters list as part of a cancellation request.</summary>
        /// <param name="waiter">The waiter to remove.</param>
        /// <returns>true if the waiter was in the list; otherwise, false.</returns>
        private bool RemoveWaiterForCancellation(ConnectionWaiter waiter)
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));
            Debug.Assert(waiter != null);
            Debug.Assert(waiter._cancellationToken.IsCancellationRequested);

            bool inList = waiter._next != null || waiter._prev != null || _waitersHead == waiter || _waitersTail == waiter;

            if (waiter._next != null)
            {
                waiter._next._prev = waiter._prev;
            }
            if (waiter._prev != null)
            {
                waiter._prev._next = waiter._next;
            }

            if (_waitersHead == waiter && _waitersTail == waiter)
            {
                _waitersHead = _waitersTail = null;
            }
            else if (_waitersHead == waiter)
            {
                _waitersHead = waiter._next;
            }
            else if (_waitersTail == waiter)
            {
                _waitersTail = waiter._prev;
            }

            waiter._next = null;
            waiter._prev = null;

            return(inList);
        }
コード例 #2
0
        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.
            }
        }
コード例 #3
0
        /// <summary>Returns the connection to the pool for subsequent reuse.</summary>
        /// <param name="connection">The connection to return.</param>
        public void ReturnConnection(HttpConnection connection)
        {
            List <CachedConnection> list = _idleConnections;

            lock (SyncObj)
            {
                Debug.Assert(list.Count <= _maxConnections, $"Expected {list.Count} <= {_maxConnections}");

                // Mark the pool as still being active.
                _usedSinceLastCleanup = true;

                // If there's someone waiting for a connection, simply
                // transfer this one to them rather than pooling it.
                if (_waitersTail != null)
                {
                    ConnectionWaiter waiter = DequeueWaiter();
                    waiter._cancellationTokenRegistration.Dispose();

                    if (NetEventSource.IsEnabled)
                    {
                        connection.Trace("Transferring connection returned to pool.");
                    }
                    waiter.SetResult(connection);

                    return;
                }

                // If the pool has been disposed of, dispose the connection being returned,
                // as the pool is being deactivated. We do this after the above in order to
                // use pooled connections to satisfy any requests that pended before the
                // the pool was disposed of.
                if (_disposed)
                {
                    if (NetEventSource.IsEnabled)
                    {
                        connection.Trace("Disposing connection returned to disposed pool.");
                    }
                    connection.Dispose();
                    return;
                }

                // Pool the connection by adding it to the list.
                list.Add(new CachedConnection(connection));
                if (NetEventSource.IsEnabled)
                {
                    connection.Trace("Stored connection in pool.");
                }
            }
        }
コード例 #4
0
        /// <summary>Enqueues a waiter to the waiters list.</summary>
        /// <param name="waiter">The waiter to add.</param>
        private void EnqueueWaiter(ConnectionWaiter waiter)
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));
            Debug.Assert(waiter != null);
            Debug.Assert(waiter._next == null);
            Debug.Assert(waiter._prev == null);

            waiter._next = _waitersHead;
            if (_waitersHead != null)
            {
                _waitersHead._prev = waiter;
            }
            else
            {
                Debug.Assert(_waitersTail == null);
                _waitersTail = waiter;
            }
            _waitersHead = waiter;
        }
コード例 #5
0
        /// <summary>Dequeues a waiter from the waiters list.  The list must not be empty.</summary>
        /// <returns>The dequeued waiter.</returns>
        private ConnectionWaiter DequeueWaiter()
        {
            Debug.Assert(Monitor.IsEntered(SyncObj));
            Debug.Assert(_waitersTail != null);

            ConnectionWaiter waiter = _waitersTail;

            _waitersTail = waiter._prev;

            if (_waitersTail != null)
            {
                _waitersTail._next = null;
            }
            else
            {
                Debug.Assert(_waitersHead == waiter);
                _waitersHead = null;
            }

            waiter._next = null;
            waiter._prev = null;

            return(waiter);
        }
コード例 #6
0
        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.
            }
        }
コード例 #7
0
        /// <summary>
        /// Decrements the number of connections associated with the pool.
        /// If there are waiters on the pool due to having reached the maximum,
        /// this will instead try to transfer the count to one of them.
        /// </summary>
        public void DecrementConnectionCount()
        {
            if (NetEventSource.IsEnabled)
            {
                Trace(null);
            }
            lock (SyncObj)
            {
                Debug.Assert(_associatedConnectionCount > 0 && _associatedConnectionCount <= _maxConnections,
                             $"Expected 0 < {_associatedConnectionCount} <= {_maxConnections}");

                // Mark the pool as not being stale.
                _usedSinceLastCleanup = true;

                if (_waitersHead == null)
                {
                    // There are no waiters to which the count should logically be transferred,
                    // so simply decrement the count.
                    _associatedConnectionCount--;
                }
                else
                {
                    // There's at least one waiter to which we should try to logically transfer
                    // the associated count.  Get the waiter.
                    Debug.Assert(_idleConnections.Count == 0, $"With {_idleConnections} connections, we shouldn't have a waiter.");
                    ConnectionWaiter waiter = DequeueWaiter();
                    Debug.Assert(waiter != null, "Expected non-null waiter");
                    Debug.Assert(waiter.Task.Status == TaskStatus.WaitingForActivation, $"Expected {waiter.Task.Status} == {nameof(TaskStatus.WaitingForActivation)}");
                    waiter._cancellationTokenRegistration.Dispose();

                    // Having a waiter means there must not be any idle connections, so we need to create
                    // one, and we do so using the logic associated with the waiter.
                    ValueTask <HttpConnection> connectionTask = waiter.CreateConnectionAsync();
                    if (connectionTask.IsCompletedSuccessfully)
                    {
                        // We synchronously and successfully created a connection (this is rare).
                        // Transfer the connection to the waiter.  Since we already have a count
                        // that's inflated due to the connection being disassociated, we don't
                        // need to change the count here.
                        waiter.SetResult(connectionTask.Result);
                    }
                    else
                    {
                        // We initiated a connection creation.  When it completes, transfer the result to the waiter.
                        connectionTask.AsTask().ContinueWith((innerConnectionTask, state) =>
                        {
                            var innerWaiter = (ConnectionWaiter)state;
                            try
                            {
                                // Get the resulting connection.
                                HttpConnection result = innerConnectionTask.GetAwaiter().GetResult();

                                // Store the resulting connection into the waiter. As in the synchronous case,
                                // since we already have a count that's inflated due to the connection being
                                // disassociated, we don't need to change the count here.
                                innerWaiter.SetResult(innerConnectionTask.Result);
                            }
                            catch (Exception e)
                            {
                                // The creation operation failed.  Store the exception into the waiter.
                                innerWaiter.SetException(e);

                                // At this point, a connection was dropped and we failed to replace it,
                                // which means our connection count still needs to be decremented.
                                innerWaiter._pool.DecrementConnectionCount();
                            }
                        }, waiter, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
                    }
                }
            }
        }
コード例 #8
0
        private ValueTask <(HttpConnection, HttpResponseMessage)> GetConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(new ValueTask <(HttpConnection, HttpResponseMessage)>(Task.FromCanceled <(HttpConnection, HttpResponseMessage)>(cancellationToken)));
            }

            TimeSpan                pooledConnectionLifetime    = _poolManager.Settings._pooledConnectionLifetime;
            TimeSpan                pooledConnectionIdleTimeout = _poolManager.Settings._pooledConnectionIdleTimeout;
            DateTimeOffset          now  = DateTimeOffset.UtcNow;
            List <CachedConnection> list = _idleConnections;

            // Try to get a cached connection.  If we can and if it's usable, return it.  If we can but it's not usable,
            // try again.  And if we can't because there aren't any valid ones, create a new one and return it.
            while (true)
            {
                CachedConnection cachedConnection;
                lock (SyncObj)
                {
                    if (list.Count > 0)
                    {
                        // Pop off the next connection to try.  We'll test it outside of the lock
                        // to avoid doing expensive validation while holding the lock.
                        cachedConnection = list[list.Count - 1];
                        list.RemoveAt(list.Count - 1);
                    }
                    else
                    {
                        // 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(CreateConnectionAsync(request, 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(this, request, 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, HttpResponseMessage)>(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.
                    }
                }

                HttpConnection conn = cachedConnection._connection;
                if (cachedConnection.IsUsable(now, pooledConnectionLifetime, pooledConnectionIdleTimeout) &&
                    !conn.EnsureReadAheadAndPollRead())
                {
                    // We found a valid connection.  Return it.
                    if (NetEventSource.IsEnabled)
                    {
                        conn.Trace("Found usable connection in pool.");
                    }
                    return(new ValueTask <(HttpConnection, HttpResponseMessage)>((conn, null)));
                }

                // 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 loop around to try again.
                if (NetEventSource.IsEnabled)
                {
                    conn.Trace("Found invalid connection in pool.");
                }
                conn.Dispose();
            }
        }