/// <summary> /// Opens one connection. /// If a connection is being opened it yields the same task, preventing creation in parallel. /// </summary> /// <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 <Connection> CreateOpenConnection(bool foreground) { 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 <Connection>(); // 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, 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, GetNotConnectedException()).ConfigureAwait(false)); } return(await FinishOpen(tcs, true, null, connectionsSnapshot[0]).ConfigureAwait(false)); } if (foreground && !_canCreateForeground) { // Foreground creation only cares about one 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, GetNotConnectedException()).ConfigureAwait(false)); } return(await FinishOpen(tcs, false, null, connectionsSnapshot[0]).ConfigureAwait(false)); } Logger.Info("Creating a new connection to {0}", _host.Address); Connection c = null; Exception creationEx = null; try { c = await DoCreateAndOpen().ConfigureAwait(false); } catch (Exception ex) { Logger.Info("Connection to {0} could not be created: {1}", _host.Address, ex); // Can not be awaited on catch on C# 5... creationEx = ex; } if (creationEx != null) { return(await FinishOpen(tcs, true, creationEx).ConfigureAwait(false)); } if (IsClosing) { Logger.Info("Connection to {0} opened successfully but pool #{1} was being closed", _host.Address, GetHashCode()); c.Dispose(); return(await FinishOpen(tcs, false, GetNotConnectedException()).ConfigureAwait(false)); } var newLength = _connections.AddNew(c); 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. 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, GetNotConnectedException()).ConfigureAwait(false)); } return(await FinishOpen(tcs, true, null, c).ConfigureAwait(false)); }