diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Thread.swift b/MailCore/Cache/MailboxManager/MailboxManager+Thread.swift index 5a8ac68ee5..e97f6d7d6f 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Thread.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Thread.swift @@ -87,7 +87,14 @@ public extension MailboxManager { let newCursor: String if let previousCursor { - newCursor = try await getMessagesDelta(signature: previousCursor, folder: folder) + do { + newCursor = try await getMessagesDelta(signature: previousCursor, folder: folder) + } catch ErrorDomain.tooManyDiffs { + try await resetFolder(folder) + + // fetch folder as if we had no cursor + newCursor = try await fetchOldMessagesUids(folder: folder) + } } else { newCursor = try await fetchOldMessagesUids(folder: folder) } @@ -161,6 +168,9 @@ public extension MailboxManager { folderId: folder.remoteId, signature: signature ) + + try messagesDelta.ensureValidDelta() + await handleDelta(messagesDelta: messagesDelta, folder: folder) return messagesDelta.cursor @@ -170,6 +180,9 @@ public extension MailboxManager { folderId: folder.remoteId, signature: signature ) + + try messagesDelta.ensureValidDelta() + await handleDelta(messagesDelta: messagesDelta, folder: folder) return messagesDelta.cursor @@ -591,6 +604,21 @@ public extension MailboxManager { writableRealm.delete(orphanMessages) } + private func resetFolder(_ folder: Folder) async throws { + try writeTransaction { realm in + guard let liveFolder = folder.fresh(using: realm) else { return } + + liveFolder.remainingOldMessagesToFetch = Constants.messageQuantityLimit + liveFolder.oldMessagesUidsToFetch.removeAll() + liveFolder.newMessagesUidsToFetch.removeAll() + realm.delete(liveFolder.threads) + realm.delete(liveFolder.messages) + liveFolder.lastUpdate = nil + liveFolder.cursor = nil + liveFolder.unreadCount = 0 + } + } + private func removeDuplicatedThreads( messageIds: MutableSet, threadsToUpdate: inout Set, diff --git a/MailCore/Cache/MailboxManager/MailboxManager.swift b/MailCore/Cache/MailboxManager/MailboxManager.swift index 0f99bacb85..794fedf01e 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager.swift @@ -42,6 +42,7 @@ public final class MailboxManager: ObservableObject, MailboxManageable { enum ErrorDomain: Error { case missingFolder case missingDraft + case tooManyDiffs } public final class MailboxManagerConstants { diff --git a/MailCore/Models/MessagesDelta.swift b/MailCore/Models/MessagesDelta.swift index 9083dd546a..536d97334f 100644 --- a/MailCore/Models/MessagesDelta.swift +++ b/MailCore/Models/MessagesDelta.swift @@ -35,6 +35,29 @@ public struct MessagesDelta: Decodable, Sendable { } } +public extension MessagesDelta { + func ensureValidDelta() throws { + let deletedCount = deletedShortUids.count + let addedCount = addedShortUids.count + let updatedCount = updated.count + + guard deletedCount < Constants.maxChangesCount, + addedCount < Constants.maxChangesCount, + updatedCount < Constants.maxChangesCount else { + SentrySDK.capture(message: "tooManyDiffs") { scope in + scope.setExtras([ + "cursor": cursor, + "deletedCount": deletedCount, + "addedCount": addedCount, + "updatedCount": updatedCount + ]) + } + + throw MailboxManager.ErrorDomain.tooManyDiffs + } + } +} + public protocol DeltaFlags: Decodable, Sendable { var shortUid: String { get } } diff --git a/MailCore/Utils/Constants.swift b/MailCore/Utils/Constants.swift index 7fcc331b0e..3e8b070fd5 100644 --- a/MailCore/Utils/Constants.swift +++ b/MailCore/Utils/Constants.swift @@ -161,6 +161,7 @@ public enum Constants { public static let messageQuantityLimit = 500 public static let numberOfOldUidsToFetch = 10000 + public static let maxChangesCount = 10000 public static let oldPageSize = 50 public static let newPageSize = 200 public static let contactSuggestionLimit = 5