internal static void MailSent(IAsyncResult result) { lock (lockObject) { MailTransmissionAsyncState asyncState = (MailTransmissionAsyncState)result.AsyncState; MailTransmissionDelegate asyncTransmitter = asyncState.dlgt; OutboundMailRecipient recipient = asyncState.recipient; try { asyncTransmitter.EndInvoke(result); try { recipient.Delete(); recipient.OutboundMail.IncrementSuccesses(); } catch (Exception ex) { // whatever.. The mail is sent, but we couldn't mark it as such, probably a database failure. asyncState.exception = new RemoveRecipientException("Couldn't remove mail recipient.", ex); ExceptionMail.Send(asyncState.exception, true); } } catch (ReportAndRetryRecipientException e) { asyncState.exception = e; Debug.WriteLine(e.ToString()); ExceptionMail.Send(e, true); } catch (RetryRecipientException e) { // No action - handled in async delegate - retry this mail asyncState.exception = e; } catch (Exception e) { asyncState.exception = e; try { recipient.Delete(); recipient.OutboundMail.IncrementFailures(); if (e is InvalidRecipientException) { if (recipient.Person != null && recipient.Person.EMailIsInvalid == false) { //Mark address as invalid. recipient.Person.EMailIsInvalid = true; } } } catch (Exception ex) { // Return this exception in asyncState since it is important to stop flooding. asyncState.exception = new RemoveRecipientException("Couldn't remove mail recipient after exception.", ex); ExceptionMail.Send(asyncState.exception, true); //Report the secondary exception } Debug.WriteLine(e.ToString()); ExceptionMail.Send(e, true); } asyncState.callbackCompleted = true; } }
public static void Run() { // If there is mail in the outbound mail queue, do not add more. (This is a primitive // protection against dupes. Better will come that doesn't force idle time like this.) lock (lockObject) { if (mailQueueSize > 0) { // return; } } // Look for a mail to process, or keep processing. OutboundMails mails = OutboundMails.GetTopUnprocessed(100); mailQueueSize = 0; if (mails.Count == 0) { // no unprocessed mail, everybody's happy return; } int maxBatchSize = 20; // Total number of recipients to process across all mails foreach (OutboundMail mail in mails) { // If we have already processed past our limit, return for now. if (maxBatchSize < 1) { return; } // We are continuing to process, and starting with a fresh outbound mail. // Is this the first time we touch this mail? if (mail.StartProcessDateTime.Year < 2000) { // Yes! Mark it as started and mail our author. mail.StartProcessDateTime = DateTime.Now; if (mail.MailType == (int)TypedMailTemplate.TemplateType.MemberMail || mail.MailType == (int)TypedMailTemplate.TemplateType.OfficerMail) // TODO: Set special flag { string mailBody = string.Empty; new MailTransmitter( Strings.MailSenderName, Strings.MailSenderAddress, "Mail transmission begins: " + mail.Title, mailBody, Person.FromIdentity(mail.AuthorPersonId), true).Send(); } } int batchSize = maxBatchSize; // If we are debugging, and stepping through this program, avoid the misery of many simultaneous threads. if (Debugger.IsAttached) { batchSize = 1; // Do NOT multithread while debugging } while (true) { if (!Debugger.IsAttached) { // HeartBeater.Instance.Beat(); //Tick the heartbeat to stop exernal restart if this takes a lot of time, but only if not debugging. } OutboundMailRecipients recipients = mail.GetNextRecipientBatch(batchSize); maxBatchSize -= recipients.Count; // Decrement number of recipients to process on next mail, if any if (recipients.Count == 0) { // This mail is complete! mail.SetProcessed(); if (mail.MailType == (int)TypedMailTemplate.TemplateType.MemberMail || mail.MailType == (int)TypedMailTemplate.TemplateType.OfficerMail) // TODO: Set special flag { string body = "Your mail has completed transmitting to " + mail.RecipientCount.ToString("#,##0") + " intended recipients. Out of these, " + mail.RecipientsFail.ToString("#,##0") + " failed because of invalid, empty, or otherwise bad e-mail addresses. These people have not received your message.\r\n"; new MailTransmitter( Strings.MailSenderName, Strings.MailSenderAddress, "Mail transmission completed: " + mail.Title, body, Person.FromIdentity(mail.AuthorPersonId), true).Send(); } if (maxBatchSize < 1) { return; } break; //the while loop } List <IAsyncResult> sendInProgress = new List <IAsyncResult>(); List <WaitHandle> waitHandlesList = new List <WaitHandle>(); foreach (OutboundMailRecipient recipient in recipients) { // Skip known invalid mail addresses if (recipient.Person != null && (recipient.Person.EMailIsInvalid || recipient.Person.MailUnreachable)) { lock (lockObject) { recipient.Delete(); recipient.OutboundMail.IncrementFailures(); } continue; } // Start the transmission process, asynchronously lock (lockObject) { MailTransmissionDelegate asyncTransmitter = TransmitOneMail; MailTransmissionAsyncState asyncState = new MailTransmissionAsyncState(); asyncState.dlgt = asyncTransmitter; asyncState.recipient = recipient; IAsyncResult asyncResult = asyncTransmitter.BeginInvoke(recipient, MailSent, asyncState); sendInProgress.Add(asyncResult); waitHandlesList.Add(asyncResult.AsyncWaitHandle); mailQueueSize++; } Thread.Sleep(25); // Allow some time } // now wait for them to finish; int numberStillExecuting = sendInProgress.Count; int numberExecutingLast = numberStillExecuting + 1; DateTime lastProgress = DateTime.Now; while (numberStillExecuting > 0) { WaitHandle.WaitAny(waitHandlesList.ToArray(), 100, true); lock (lockObject) { numberStillExecuting = 0; waitHandlesList = new List <WaitHandle>(); for (int i = 0; i < sendInProgress.Count; ++i) { IAsyncResult iares = sendInProgress[i]; MailTransmissionAsyncState asyncState = (MailTransmissionAsyncState)iares.AsyncState; if (asyncState.dlgt != null) { if (!asyncState.callbackCompleted) { waitHandlesList.Add(iares.AsyncWaitHandle); numberStillExecuting++; } else { //Just finalised if (asyncState.exception != null) { if (asyncState.exception is RetryRecipientException) { //Failed in sending due to some reason that can clear up by itself. asyncState.dlgt = null; } else { //Make sure recipient is deleted try { asyncState.recipient.Delete(); // if RemoveRecipientException everything went ok except for the removal if (asyncState.exception is RemoveRecipientException) { asyncState.recipient.OutboundMail.IncrementSuccesses(); } asyncState.dlgt = null; // mark as done; mailQueueSize--; } catch { //Keep looping until recipient removed numberStillExecuting++; } } } } } } } if (numberExecutingLast != numberStillExecuting) { lastProgress = DateTime.Now; } numberExecutingLast = numberStillExecuting; if (lastProgress.AddSeconds(60 * 2) < DateTime.Now) { // since last change, something must have hanged lock (lockObject) { mailQueueSize = -1000; } throw new Exception("Timeout in MailProcessor"); } } } } }
public static void Run () { // If there is mail in the outbound mail queue, do not add more. (This is a primitive // protection against dupes. Better will come that doesn't force idle time like this.) lock (lockObject) { if (mailQueueSize > 0) { // return; } } // Look for a mail to process, or keep processing. OutboundMails mails = OutboundMails.GetTopUnprocessed(100); mailQueueSize = 0; if (mails.Count == 0) { // no unprocessed mail, everybody's happy return; } int maxBatchSize = 20; // Total number of recipients to process across all mails foreach (OutboundMail mail in mails) { // If we have already processed past our limit, return for now. if (maxBatchSize < 1) { return; } // We are continuing to process, and starting with a fresh outbound mail. // Is this the first time we touch this mail? if (mail.StartProcessDateTime.Year < 2000) { // Yes! Mark it as started and mail our author. mail.StartProcessDateTime = DateTime.Now; if (mail.MailType == (int)TypedMailTemplate.TemplateType.MemberMail || mail.MailType == (int)TypedMailTemplate.TemplateType.OfficerMail) // TODO: Set special flag { string mailBody = string.Empty; new MailTransmitter( Strings.MailSenderName, Strings.MailSenderAddress, "Mail transmission begins: " + mail.Title, mailBody, Person.FromIdentity(mail.AuthorPersonId), true).Send(); } } int batchSize = maxBatchSize; // If we are debugging, and stepping through this program, avoid the misery of many simultaneous threads. if (System.Diagnostics.Debugger.IsAttached) { batchSize = 1; // Do NOT multithread while debugging } while (true) { if (!System.Diagnostics.Debugger.IsAttached) { HeartBeater.Instance.Beat(); //Tick the heartbeat to stop exernal restart if this takes a lot of time, but only if not debugging. } OutboundMailRecipients recipients = mail.GetNextRecipientBatch(batchSize); maxBatchSize -= recipients.Count; // Decrement number of recipients to process on next mail, if any if (recipients.Count == 0) { // This mail is complete! mail.SetProcessed(); if (mail.MailType == (int)TypedMailTemplate.TemplateType.MemberMail || mail.MailType == (int)TypedMailTemplate.TemplateType.OfficerMail) // TODO: Set special flag { string body = "Your mail has completed transmitting to " + mail.RecipientCount.ToString("#,##0") + " intended recipients. Out of these, " + mail.RecipientsFail.ToString("#,##0") + " failed because of invalid, empty, or otherwise bad e-mail addresses. These people have not received your message.\r\n"; new Mail.MailTransmitter( Strings.MailSenderName, Strings.MailSenderAddress, "Mail transmission completed: " + mail.Title, body, Person.FromIdentity(mail.AuthorPersonId), true).Send(); } if (maxBatchSize < 1) { return; } break; //the while loop } else { List<IAsyncResult> sendInProgress = new List<IAsyncResult>(); List<WaitHandle> waitHandlesList = new List<WaitHandle>(); foreach (OutboundMailRecipient recipient in recipients) { // Skip known invalid mail addresses if (recipient.Person != null && (recipient.Person.EMailIsInvalid || recipient.Person.MailUnreachable)) { lock (lockObject) { recipient.Delete(); recipient.OutboundMail.IncrementFailures(); } continue; } // Start the transmission process, asynchronously lock (lockObject) { MailTransmissionDelegate asyncTransmitter = new MailTransmissionDelegate(TransmitOneMail); MailTransmissionAsyncState asyncState = new MailTransmissionAsyncState(); asyncState.dlgt = asyncTransmitter; asyncState.recipient = recipient; IAsyncResult asyncResult = asyncTransmitter.BeginInvoke(recipient, new AsyncCallback(MailSent), asyncState); sendInProgress.Add(asyncResult); waitHandlesList.Add(asyncResult.AsyncWaitHandle); mailQueueSize++; } System.Threading.Thread.Sleep(25); // Allow some time } // now wait for them to finish; int numberStillExecuting = sendInProgress.Count; int numberExecutingLast = numberStillExecuting + 1; DateTime lastProgress = DateTime.Now; while (numberStillExecuting > 0) { WaitHandle.WaitAny(waitHandlesList.ToArray(), 100, true); lock (lockObject) { numberStillExecuting = 0; waitHandlesList = new List<WaitHandle>(); for (int i = 0; i < sendInProgress.Count; ++i) { IAsyncResult iares = sendInProgress[i]; MailTransmissionAsyncState asyncState = (MailTransmissionAsyncState)iares.AsyncState; if (asyncState.dlgt != null) { if (!asyncState.callbackCompleted) { waitHandlesList.Add(iares.AsyncWaitHandle); numberStillExecuting++; } else { //Just finalised if (asyncState.exception != null) { if (asyncState.exception is RetryRecipientException) { //Failed in sending due to some reason that can clear up by itself. asyncState.dlgt = null; } else { //Make sure recipient is deleted try { asyncState.recipient.Delete(); // if RemoveRecipientException everything went ok except for the removal if (asyncState.exception is RemoveRecipientException) asyncState.recipient.OutboundMail.IncrementSuccesses(); asyncState.dlgt = null; // mark as done; mailQueueSize--; } catch { //Keep looping until recipient removed numberStillExecuting++; } } } } } } } if (numberExecutingLast != numberStillExecuting) lastProgress = DateTime.Now; numberExecutingLast = numberStillExecuting; if (lastProgress.AddSeconds(60 * 2) < DateTime.Now) { // since last change, something must have hanged lock (lockObject) { mailQueueSize = -1000; } throw new Exception("Timeout in MailProcessor"); } } } } } }