From 087f638b878017318efea39051958c3d0f130f27 Mon Sep 17 00:00:00 2001 From: Daniel Collingwood <82693586+danzuep@users.noreply.github.com> Date: Thu, 8 Aug 2024 01:01:08 +0800 Subject: [PATCH] #72 added retry to arrival and departure queues --- .../Extensions/EmailReceiverExtensions.cs | 18 +++-- .../Extensions/LoggerExtensions.cs | 25 +++++++ .../Services/MailFolderMonitor.cs | 75 +++++++++++-------- 3 files changed, 79 insertions(+), 39 deletions(-) create mode 100644 source/MailKitSimplified.Receiver/Extensions/LoggerExtensions.cs diff --git a/source/MailKitSimplified.Receiver/Extensions/EmailReceiverExtensions.cs b/source/MailKitSimplified.Receiver/Extensions/EmailReceiverExtensions.cs index 2951549..056ab72 100644 --- a/source/MailKitSimplified.Receiver/Extensions/EmailReceiverExtensions.cs +++ b/source/MailKitSimplified.Receiver/Extensions/EmailReceiverExtensions.cs @@ -1,10 +1,11 @@ -using MailKit; -using MailKit.Search; -using System; -using System.Linq; -using System.Threading; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.Json; +using System.Threading; +using MailKit; +using MailKit.Search; namespace MailKitSimplified.Receiver.Extensions { @@ -12,8 +13,11 @@ namespace MailKitSimplified.Receiver.Extensions public static class EmailReceiverExtensions { /// - public static string ToEnumeratedString(this IEnumerable data, string div = ", ") => - data is null ? "" : string.Join(div, data.Select(o => o?.ToString() ?? "")); + public static string ToEnumeratedString(this IEnumerable data, string delimiter = ", ") => + data is null ? string.Empty : string.Join(delimiter, data.Select(o => o?.ToString() ?? string.Empty)); + + public static string ToSerializedString(this object obj) => + obj != null ? JsonSerializer.Serialize(obj) : string.Empty; /// public static IList TryAddUniqueRange(this IList list, IEnumerable items) where T : IMessageSummary { diff --git a/source/MailKitSimplified.Receiver/Extensions/LoggerExtensions.cs b/source/MailKitSimplified.Receiver/Extensions/LoggerExtensions.cs new file mode 100644 index 0000000..6632ca8 --- /dev/null +++ b/source/MailKitSimplified.Receiver/Extensions/LoggerExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Logging; + +namespace MailKitSimplified.Receiver.Extensions +{ + /// + /// + /// + [ExcludeFromCodeCoverage] + public static class LoggerExtensions + { + public static void Serialized(this ILogger logger, T obj, LogLevel logLevel = LogLevel.Information) where T : class => + logger.Log(logLevel, "\"{Name}\": {JsonSerializedObject}", typeof(T).Name, obj.ToSerializedString()); + + internal static Action LogAction(string message, LogLevel logLevel, int id) => + LoggerMessage.Define(logLevel, new EventId(id, name: typeof(T).Name), message); + + public static void Log(this ILogger logger, string message, LogLevel logLevel = LogLevel.Information, int id = 0) => + LogAction(message, logLevel, id)(logger, null); + + public static void Log(this ILogger logger, Exception ex, string message, LogLevel logLevel = LogLevel.Error, int id = 1) => + LogAction(message, logLevel, id)(logger, ex); + } +} \ No newline at end of file diff --git a/source/MailKitSimplified.Receiver/Services/MailFolderMonitor.cs b/source/MailKitSimplified.Receiver/Services/MailFolderMonitor.cs index cc699a5..726143c 100644 --- a/source/MailKitSimplified.Receiver/Services/MailFolderMonitor.cs +++ b/source/MailKitSimplified.Receiver/Services/MailFolderMonitor.cs @@ -318,10 +318,11 @@ private async ValueTask WaitForNewMessagesAsync(CancellationToken cancellationTo } catch (ImapProtocolException ex) { + var message = $"{ex.Message} Reconnecting and trying again."; if (ex.Message.StartsWith("Idle timeout")) - _logger.LogDebug($"{ex.Message} Trying again."); + _logger.Log(message, LogLevel.Debug); else - _logger.LogInformation(ex, "IMAP protocol exception, checking connection."); + _logger.Log(message, LogLevel.Information); await ReconnectAsync(cancellationToken).ConfigureAwait(false); if (_folderMonitorOptions.IdleMinutes > FolderMonitorOptions.IdleMinutesGmail) _folderMonitorOptions.IdleMinutes = FolderMonitorOptions.IdleMinutesGmail; @@ -392,59 +393,69 @@ private async ValueTask ProcessMessagesArrivedAsync(bool firstConnection = private async Task ProcessArrivalQueueAsync(Func messageArrivalMethod, CancellationToken cancellationToken = default) { - IMessageSummary messageSummary = null; - try + int retryCount = 0; + if (messageArrivalMethod != null) { - if (messageArrivalMethod != null) + IMessageSummary messageSummary = null; + do { - do + retryCount++; + try { if (_arrivalQueue.TryDequeue(out messageSummary)) await messageArrivalMethod(messageSummary).ConfigureAwait(false); else if (_arrivalQueue.IsEmpty) await Task.Delay(_folderMonitorOptions.EmptyQueueMaxDelayMs, cancellationToken).ConfigureAwait(false); + retryCount = 0; + } + catch (OperationCanceledException) + { + _logger.LogTrace("Arrival queue cancelled."); + break; + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"Error occurred processing arrival queue item, backing off for {_folderMonitorOptions.EmptyQueueMaxDelayMs}ms. {_imapReceiver} #{messageSummary.UniqueId}."); + if (messageSummary != null) + _arrivalQueue.Enqueue(messageSummary); + await Task.Delay(_folderMonitorOptions.EmptyQueueMaxDelayMs, cancellationToken).ConfigureAwait(false); } - while (!cancellationToken.IsCancellationRequested); } - } - catch (OperationCanceledException) - { - _logger.LogTrace("Arrival queue cancelled."); - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"Error occurred processing arrival queue item. {_imapReceiver} #{messageSummary.UniqueId}."); - if (messageSummary != null) - _arrivalQueue.Enqueue(messageSummary); + while (!cancellationToken.IsCancellationRequested && retryCount < _folderMonitorOptions.MaxRetries); } } private async Task ProcessDepartureQueueAsync(Func messageDepartureMethod, CancellationToken cancellationToken = default) { - IMessageSummary messageSummary = null; - try + int retryCount = 0; + if (messageDepartureMethod != null) { - if (messageDepartureMethod != null) + IMessageSummary messageSummary = null; + do { - do + retryCount++; + try { if (_departureQueue.TryDequeue(out messageSummary)) await messageDepartureMethod(messageSummary).ConfigureAwait(false); else if (_departureQueue.IsEmpty) await Task.Delay(_folderMonitorOptions.EmptyQueueMaxDelayMs, cancellationToken).ConfigureAwait(false); + retryCount = 0; + } + catch (OperationCanceledException) + { + _logger.LogTrace("Departure queue cancelled."); + break; + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"Error occurred processing departure queue item, backing off for {_folderMonitorOptions.EmptyQueueMaxDelayMs}ms. {_imapReceiver} #{messageSummary.UniqueId}."); + if (messageSummary != null) + _departureQueue.Enqueue(messageSummary); + await Task.Delay(_folderMonitorOptions.EmptyQueueMaxDelayMs, cancellationToken).ConfigureAwait(false); } - while (!cancellationToken.IsCancellationRequested); } - } - catch (OperationCanceledException) - { - _logger.LogTrace("Departure queue cancelled."); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error occurred processing departure queue item. {_imapReceiver} #{messageSummary.UniqueId}."); - if (messageSummary != null) - _departureQueue.Enqueue(messageSummary); + while (!cancellationToken.IsCancellationRequested && retryCount < _folderMonitorOptions.MaxRetries); } }