Compare commits

...

1 Commits

Author SHA1 Message Date
1c00512661 Improve IMAP reconnection and error handling in polling
Refactored PollInboxAsync to better handle IMAP connection drops and protocol errors by reconnecting and resuming processing as needed. Switched from using a using statement to explicit disposal in a finally block. Now logs and recovers from transient IMAP issues, and ensures proper cancellation handling by rethrowing OperationCanceledException. This increases service robustness and reliability.
2026-04-12 14:23:16 +01:00

View File

@@ -90,62 +90,84 @@ public sealed partial class InboxMonitorService : BackgroundService
private async Task PollInboxAsync(CancellationToken ct) private async Task PollInboxAsync(CancellationToken ct)
{ {
using var client = await _imapFactory.CreateConnectedClientAsync(_imap, ct); var client = await _imapFactory.CreateConnectedClientAsync(_imap, ct);
var inbox = client.Inbox; try
await inbox.OpenAsync(FolderAccess.ReadWrite, ct); {
var inbox = client.Inbox;
await inbox.OpenAsync(FolderAccess.ReadWrite, ct);
// Build search query: only fetch new messages // Build search query: only fetch new messages
IList<UniqueId> uids; IList<UniqueId> uids;
if (_lastSeenUid > 0) if (_lastSeenUid > 0)
{
var range = new UniqueIdRange(new UniqueId(_lastSeenUid + 1), UniqueId.MaxValue);
uids = await inbox.SearchAsync(range, SearchQuery.All, ct);
}
else if (_processedUids.Count > 0)
{
// Resuming from persisted state -- scan recent messages only
uids = await inbox.SearchAsync(
SearchQuery.DeliveredAfter(DateTime.UtcNow.AddDays(-1)), ct);
}
else
{
// First ever run -- initial scan window
uids = await inbox.SearchAsync(
SearchQuery.DeliveredAfter(DateTime.UtcNow.AddDays(-_monitoring.InitialScanDays)), ct);
}
_logger.LogDebug("Found {Count} messages in inbox", uids.Count);
// Find the spam/junk folder
var spamFolder = await FindSpamFolderAsync(client, ct);
foreach (var uid in uids)
{
if (_processedUids.Contains(uid.Id))
{ {
if (uid.Id > _lastSeenUid) _lastSeenUid = uid.Id; var range = new UniqueIdRange(new UniqueId(_lastSeenUid + 1), UniqueId.MaxValue);
continue; uids = await inbox.SearchAsync(range, SearchQuery.All, ct);
} }
else if (_processedUids.Count > 0)
try
{ {
await ProcessMessageAsync(inbox, uid, spamFolder, ct); // Resuming from persisted state -- scan recent messages only
uids = await inbox.SearchAsync(
SearchQuery.DeliveredAfter(DateTime.UtcNow.AddDays(-1)), ct);
} }
catch (Exception ex) else
{ {
_logger.LogError(ex, "Error processing UID={Uid}", uid.Id); // First ever run -- initial scan window
_activityLog.Add(new ActivityEntry uids = await inbox.SearchAsync(
SearchQuery.DeliveredAfter(DateTime.UtcNow.AddDays(-_monitoring.InitialScanDays)), ct);
}
_logger.LogDebug("Found {Count} messages in inbox", uids.Count);
// Find the spam/junk folder
var spamFolder = await FindSpamFolderAsync(client, ct);
foreach (var uid in uids)
{
if (_processedUids.Contains(uid.Id))
{ {
Timestamp = DateTime.UtcNow, Sender = "", Subject = $"UID {uid.Id}", if (uid.Id > _lastSeenUid) _lastSeenUid = uid.Id;
Verdict = Verdict.Error, Reason = ex.Message, continue;
Uid = uid.Id, AccountName = AccountName }
});
_processedUids.Add(uid.Id); try
{
await ProcessMessageAsync(inbox, uid, spamFolder, ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing UID={Uid}", uid.Id);
_activityLog.Add(new ActivityEntry
{
Timestamp = DateTime.UtcNow, Sender = "", Subject = $"UID {uid.Id}",
Verdict = Verdict.Error, Reason = ex.Message,
Uid = uid.Id, AccountName = AccountName
});
_processedUids.Add(uid.Id);
// Reconnect if the server dropped the connection (e.g. unexpected response after MoveToAsync)
if (!client.IsConnected || ex is MailKit.Net.Imap.ImapProtocolException)
{
_logger.LogWarning("IMAP connection lost for {Account}, reconnecting", AccountName);
try { client.Dispose(); } catch { }
client = await _imapFactory.CreateConnectedClientAsync(_imap, ct);
inbox = client.Inbox;
await inbox.OpenAsync(FolderAccess.ReadWrite, ct);
spamFolder = await FindSpamFolderAsync(client, ct);
}
}
if (uid.Id > _lastSeenUid) _lastSeenUid = uid.Id;
} }
if (uid.Id > _lastSeenUid) _lastSeenUid = uid.Id; await client.DisconnectAsync(true, ct);
}
finally
{
client.Dispose();
} }
await client.DisconnectAsync(true, ct);
} }
private async Task ProcessMessageAsync( private async Task ProcessMessageAsync(