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); }
/// <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)); }