Exemple #1
0
        private IConnection BorrowLeastBusyConnection(IConnection[] connections)
        {
            var c = HostConnectionPool.MinInFlight(connections, ref _connectionIndex, _maxRequestsPerConnection, out var inFlight);

            if (inFlight >= _maxRequestsPerConnection)
            {
                throw new BusyPoolException(_host.Address, _maxRequestsPerConnection, connections.Length);
            }

            ConsiderResizingPool(inFlight);
            return(c);
        }
Exemple #2
0
        /// <summary>
        /// Gets existing connections snapshot.
        /// If it's empty then it validates whether the pool is shutting down or the is down (in which case an exception is thrown).
        /// </summary>
        /// <exception cref="SocketException">Not connected.</exception>
        private IConnection[] GetExistingConnections()
        {
            var connections = _connections.GetSnapshot();

            if (connections.Length > 0)
            {
                return(connections);
            }

            if (IsClosing || !_host.IsUp)
            {
                // Should have not been considered as UP
                throw HostConnectionPool.GetNotConnectedException();
            }

            return(connections);
        }
        /// <inheritdoc />
        public async Task <IConnection> BorrowConnection()
        {
            var connections = await EnsureCreate().ConfigureAwait(false);

            if (connections.Length == 0)
            {
                throw new DriverInternalError("No connection could be borrowed");
            }

            var c = HostConnectionPool.MinInFlight(connections, ref _connectionIndex, _maxRequestsPerConnection, out var inFlight);

            if (inFlight >= _maxRequestsPerConnection)
            {
                throw new BusyPoolException(c.Address, _maxRequestsPerConnection, connections.Length);
            }

            ConsiderResizingPool(inFlight);
            return(c);
        }
        /// <summary>
        /// Ensures that the pool has at least contains 1 connection to the host.
        /// </summary>
        /// <returns>An Array of connections with 1 or more elements or throws an exception.</returns>
        /// <exception cref="SocketException" />
        /// <exception cref="AuthenticationException" />
        /// <exception cref="UnsupportedProtocolVersionException" />
        public async Task <IConnection[]> EnsureCreate()
        {
            var connections = _connections.GetSnapshot();

            if (connections.Length > 0)
            {
                // Use snapshot to return as early as possible
                return(connections);
            }
            if (IsClosing || !_host.IsUp)
            {
                // Should have not been considered as UP
                throw HostConnectionPool.GetNotConnectedException();
            }
            if (!_canCreateForeground)
            {
                // Take a new snapshot
                connections = _connections.GetSnapshot();
                if (connections.Length > 0)
                {
                    return(connections);
                }
                // It's not considered as connected
                throw HostConnectionPool.GetNotConnectedException();
            }
            IConnection c;

            try
            {
                // It should only await for the creation of the connection in few selected occasions:
                // It's the first time accessing or it has been recently set as UP
                // CreateOpenConnection() supports concurrent calls
                c = await CreateOpenConnection(true).ConfigureAwait(false);
            }
            catch (Exception)
            {
                OnConnectionClosing();
                throw;
            }
            StartCreatingConnection(null);
            return(new[] { c });
        }
        /// <summary>
        /// Opens one connection.
        /// If a connection is being opened it yields the same task, preventing creation in parallel.
        /// </summary>
        /// <param name="satisfyWithAnOpenConnection">
        /// Determines whether the Task should be marked as completed when there is a connection already opened.
        /// </param>
        /// <exception cref="SocketException">Throws a SocketException when the connection could not be established with the host</exception>
        /// <exception cref="AuthenticationException" />
        /// <exception cref="UnsupportedProtocolVersionException" />
        private async Task <IConnection> CreateOpenConnection(bool satisfyWithAnOpenConnection)
        {
            var concurrentOpenTcs = Volatile.Read(ref _connectionOpenTcs);

            // Try to exit early (cheap) as there could be another thread creating / finishing creating
            if (concurrentOpenTcs != null)
            {
                // There is another thread opening a new connection
                return(await concurrentOpenTcs.Task.ConfigureAwait(false));
            }
            var tcs = new TaskCompletionSource <IConnection>();

            // Try to set the creation task source
            concurrentOpenTcs = Interlocked.CompareExchange(ref _connectionOpenTcs, tcs, null);
            if (concurrentOpenTcs != null)
            {
                // There is another thread opening a new connection
                return(await concurrentOpenTcs.Task.ConfigureAwait(false));
            }

            if (IsClosing)
            {
                return(await FinishOpen(tcs, false, HostConnectionPool.GetNotConnectedException()).ConfigureAwait(false));
            }

            // Before creating, make sure that its still needed
            // This method is the only one that adds new connections
            // But we don't control the removal, use snapshot
            var connectionsSnapshot = _connections.GetSnapshot();

            if (connectionsSnapshot.Length >= _expectedConnectionLength)
            {
                if (connectionsSnapshot.Length == 0)
                {
                    // Avoid race condition while removing
                    return(await FinishOpen(tcs, false, HostConnectionPool.GetNotConnectedException()).ConfigureAwait(false));
                }
                return(await FinishOpen(tcs, true, null, connectionsSnapshot[0]).ConfigureAwait(false));
            }

            if (satisfyWithAnOpenConnection && !_canCreateForeground)
            {
                // We only care about a single connection, if its already there, yield it
                connectionsSnapshot = _connections.GetSnapshot();
                if (connectionsSnapshot.Length == 0)
                {
                    // When creating in foreground, it failed
                    return(await FinishOpen(tcs, false, HostConnectionPool.GetNotConnectedException()).ConfigureAwait(false));
                }
                return(await FinishOpen(tcs, false, null, connectionsSnapshot[0]).ConfigureAwait(false));
            }

            HostConnectionPool.Logger.Info("Creating a new connection to {0}", _host.Address);
            IConnection c;

            try
            {
                c = await DoCreateAndOpen().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                HostConnectionPool.Logger.Info("Connection to {0} could not be created: {1}", _host.Address, ex);
                return(await FinishOpen(tcs, true, ex).ConfigureAwait(false));
            }

            if (IsClosing)
            {
                HostConnectionPool.Logger.Info("Connection to {0} opened successfully but pool #{1} was being closed",
                                               _host.Address, GetHashCode());
                c.Dispose();
                return(await FinishOpen(tcs, false, HostConnectionPool.GetNotConnectedException()).ConfigureAwait(false));
            }

            var newLength = _connections.AddNew(c);

            HostConnectionPool.Logger.Info("Connection to {0} opened successfully, pool #{1} length: {2}",
                                           _host.Address, GetHashCode(), newLength);

            if (IsClosing)
            {
                // We haven't use a CAS operation, so it's possible that the pool is being closed while adding a new
                // connection, we should remove it.
                HostConnectionPool.Logger.Info("Connection to {0} opened successfully and added to the pool #{1} but it was being closed",
                                               _host.Address, GetHashCode());
                _connections.Remove(c);
                c.Dispose();
                return(await FinishOpen(tcs, false, HostConnectionPool.GetNotConnectedException()).ConfigureAwait(false));
            }

            return(await FinishOpen(tcs, true, null, c).ConfigureAwait(false));
        }