/// <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)); }
private SmtpClientPool() { new Thread(new ThreadStart(delegate { while (true) { try { IEnumerator <KeyValuePair <string, SmtpClientMxRecords> > enumMXs = this._OutboundConnections.GetEnumerator(); while (enumMXs.MoveNext()) { int count = 0; int removed = 0; KeyValuePair <string, SmtpClientMxRecords> current = enumMXs.Current; SmtpClientMxRecords mxRecords = current.Value; IEnumerator <KeyValuePair <string, SmtpClientQueue> > enumClients = mxRecords.GetEnumerator(); while (enumClients.MoveNext()) { count++; KeyValuePair <string, SmtpClientQueue> current2 = enumClients.Current; SmtpClientQueue clients = current2.Value; if (clients.Count == 0 && clients.InUseConnections.Count == 0) { ConcurrentDictionary <string, SmtpClientQueue> arg_8B_0 = mxRecords; current2 = enumClients.Current; if (arg_8B_0.TryRemove(current2.Key, out clients)) { //string arg_AF_0 = "Removed empty SMTP Clients Queue for "; //current2 = enumClients.Current; //Logging.Debug(arg_AF_0 + current2.Key); removed++; } else { string arg_D6_0 = "Failed to remove empty SMTP Clients Queue for "; current2 = enumClients.Current; Logging.Debug(arg_D6_0 + current2.Key); } } } /*Logging.Debug(string.Concat(new string[] * { * "SmtpClientPool : Removed ", * removed.ToString("N0"), * " client queues. ", * (count - removed).ToString("N0"), * " remaining." * }));*/ } } catch (Exception ex) { Logging.Debug("SmtpClientPool: " + ex); } finally { Thread.Sleep(60000); } } })) { IsBackground = true }.Start(); }