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.
This commit is contained in:
2026-04-12 14:23:16 +01:00
parent 387c0dc155
commit 1c00512661

View File

@@ -90,7 +90,9 @@ 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);
try
{
var inbox = client.Inbox; var inbox = client.Inbox;
await inbox.OpenAsync(FolderAccess.ReadWrite, ct); await inbox.OpenAsync(FolderAccess.ReadWrite, ct);
@@ -130,6 +132,10 @@ public sealed partial class InboxMonitorService : BackgroundService
{ {
await ProcessMessageAsync(inbox, uid, spamFolder, ct); await ProcessMessageAsync(inbox, uid, spamFolder, ct);
} }
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw;
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error processing UID={Uid}", uid.Id); _logger.LogError(ex, "Error processing UID={Uid}", uid.Id);
@@ -140,6 +146,17 @@ public sealed partial class InboxMonitorService : BackgroundService
Uid = uid.Id, AccountName = AccountName Uid = uid.Id, AccountName = AccountName
}); });
_processedUids.Add(uid.Id); _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;
@@ -147,6 +164,11 @@ public sealed partial class InboxMonitorService : BackgroundService
await client.DisconnectAsync(true, ct); await client.DisconnectAsync(true, ct);
} }
finally
{
client.Dispose();
}
}
private async Task ProcessMessageAsync( private async Task ProcessMessageAsync(
IMailFolder inbox, UniqueId uid, IMailFolder? spamFolder, CancellationToken ct) IMailFolder inbox, UniqueId uid, IMailFolder? spamFolder, CancellationToken ct)