/// <summary> /// Enqueue the SmtpOutboundClient for use by another message. /// </summary> /// <param name="client">The client to queue.</param> public void Enqueue(SmtpOutboundClient client) { SmtpClientMxRecords mxConnections = this._OutboundConnections.GetOrAdd(client.SmtpStream.LocalAddress.ToString(), new SmtpClientMxRecords()); SmtpClientQueue clientQueue = null; lock (this._ClientPoolLock) { if (!mxConnections.TryGetValue(client.MXRecord.Host, out clientQueue)) { clientQueue = new SmtpClientQueue(); if (!mxConnections.TryAdd(client.MXRecord.Host, clientQueue)) { throw new Exception("Failed to add new SmtpClientQueue"); } } } clientQueue.Enqueue(client); try { clientQueue.InUseConnections.Remove(client); } catch (Exception) { // Already removed. } }
/// <summary> /// Attempts to get a SmtpClient using the outbound IP address and the specified MX records collection. /// /// WARNING: returned SmtpOutboundClient will have it's IsActive flag set to true make sure to set it to /// false when done with it or it will never be removed by the idle timeout. /// </summary> /// <param name="ipAddress">The local outbound endpoint we wan't to use.</param> /// <param name="mxs">The MX records for the domain we wan't a client to connect to.</param> /// <returns>Tuple containing a DequeueAsyncResult and either an SmtpOutboundClient or Null if failed to dequeue.</returns> public async Task <SmtpOutboundClientDequeueResponse> DequeueAsync(VirtualMTA ipAddress, MXRecord[] mxs) { // If there aren't any remote mx records then we can't send if (mxs.Length < 1) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.NoMxRecords)); } SmtpClientMxRecords mxConnections = this._OutboundConnections.GetOrAdd(ipAddress.IPAddress.ToString(), new SmtpClientMxRecords()); SmtpOutboundClient smtpClient = null; // Check that we aren't being throttled. if (!ThrottleManager.Instance.TryGetSendAuth(ipAddress, mxs[0])) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.Throttled)); } // Loop through all the MX Records. for (int i = 0; i < mxs.Length; i++) { try { // To prevent us bombarding a server that is blocking us we will check the service not available manager // to see if we can send to this MX, Max 1 message/minute, if we can't we won't. // At the moment we stop to all MXs for a domain if one of them responds with service unavailable. // This could be improved to allow others to continue, we should however if blocked on all MX's with // lowest preference not move on to the others. if (ServiceNotAvailableManager.IsServiceUnavailable(ipAddress.IPAddress.ToString(), mxs[i].Host)) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.ServiceUnavalible)); } SmtpClientQueue clientQueue = null; lock (_ClientPoolLock) { if (!mxConnections.TryGetValue(mxs[i].Host, out clientQueue)) { clientQueue = new SmtpClientQueue(); if (!mxConnections.TryAdd(mxs[i].Host, clientQueue)) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.FailedToAddToSmtpClientQueue)); } } } // Loop through the client queue and make sure we get one thats still connected. // They may have idled out while waiting. while (!clientQueue.IsEmpty) { if (clientQueue.TryDequeue(out smtpClient)) { if (smtpClient.Connected) { clientQueue.InUseConnections.Add(smtpClient); smtpClient.LastActive = DateTime.UtcNow; smtpClient.IsActive = true; return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.Success, smtpClient)); } } } // Nothing was in the queue or all queued items timed out. CreateNewConnectionAsyncResult createNewConnectionResult = await clientQueue.CreateNewConnectionAsync(ipAddress, mxs[i]); if (createNewConnectionResult.Exception == null) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.Success, createNewConnectionResult.OutboundClient)); } else if (createNewConnectionResult.Exception is MaxConnectionsException) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.FailedMaxConnections)); } else { throw createNewConnectionResult.Exception; } } catch (SocketException) { // We have failed to connect to the remote host. // Logging.Warn("Failed to connect to " + mxs[i].Host, ex); // If we fail to connect to an MX then don't try again for at least a minute. ServiceNotAvailableManager.Add(ipAddress.IPAddress.ToString(), mxs[i].Host, DateTime.UtcNow); // Failed to connect to MX if (i == mxs.Length - 1) { return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.FailedToConnect)); } } catch (Exception ex) { // Something unexpected and unhandled has happened, log it and return unknown. Logging.Error("SmtpClientPool.DequeueAsync Unhandled Exception", ex); return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.Unknown)); } } // It we got here then we have failed to connect to the remote host. return(new SmtpOutboundClientDequeueResponse(SmtpOutboundClientDequeueAsyncResult.FailedToConnect)); }