Skip to content

Commit

Permalink
Merge pull request #998 from Infomaniak/fix-message-not-found
Browse files Browse the repository at this point in the history
fix: Handle message not found
  • Loading branch information
Ambrdctr committed Oct 3, 2023
2 parents 1bb17bf + 407e572 commit 3ca6960
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Infomaniak/ios-core",
"state" : {
"revision" : "025c5dffa801d0ffe4e55730897466784f42564f"
"revision" : "1c741dfd8096fcd0c83200d958917d246ad9e6f2"
}
},
{
Expand Down
1 change: 1 addition & 0 deletions MailCore/API/MailError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class MailError: LocalizedError {
public static let noConnection = MailError(code: "noConnection",
localizedDescription: MailResourcesStrings.Localizable.noConnection,
shouldDisplay: true)
static let lostOffsetMessage = MailError(code: "lostOffsetMessage")

/// After an update from the server we are still without a default signature
public static let defaultSignatureMissing = MailError(code: "defaultSignatureMissing")
Expand Down
2 changes: 1 addition & 1 deletion MailCore/Cache/MailboxManager/MailboxManageable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public typealias MailboxManageable = MailBoxManagerDraftable & MailBoxManagerMes

/// An abstract interface on the `MailboxManager` related to messages
public protocol MailBoxManagerMessageable {
func messages(folder: Folder) async throws
func messages(folder: Folder, isRetrying: Bool) async throws
func fetchOnePage(folder: Folder, direction: NewMessagesDirection?) async throws -> Bool
func message(message: Message) async throws
func attachmentData(attachment: Attachment) async throws -> Data
Expand Down
135 changes: 92 additions & 43 deletions MailCore/Cache/MailboxManager/MailboxManager+Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import CocoaLumberjackSwift
import Foundation
import InfomaniakCoreUI
import InfomaniakDI
Expand All @@ -26,7 +27,7 @@ import Sentry
// MARK: - Message

public extension MailboxManager {
func messages(folder: Folder) async throws {
func messages(folder: Folder, isRetrying: Bool = false) async throws {
guard !Task.isCancelled else { return }

let realm = getRealm()
Expand Down Expand Up @@ -93,8 +94,10 @@ public extension MailboxManager {
}

if previousCursor != nil {
while try await fetchOnePage(folder: folder, direction: .following) {
guard !Task.isCancelled else { return }
try await catchLostOffsetMessageError(folder: folder, isRetrying: isRetrying) {
while try await fetchOnePage(folder: folder, direction: .following) {
guard !Task.isCancelled else { return }
}
}
}

Expand All @@ -104,26 +107,64 @@ public extension MailboxManager {
}

let realmPrevious = getRealm()
if let folderPrevious = folder.fresh(using: realmPrevious) {
var remainingOldMessagesToFetch = folderPrevious.remainingOldMessagesToFetch
while remainingOldMessagesToFetch > 0 {
guard !Task.isCancelled else { return }
guard let folderPrevious = folder.fresh(using: realmPrevious) else { return }
var remainingOldMessagesToFetch = folderPrevious.remainingOldMessagesToFetch
while remainingOldMessagesToFetch > 0 {
guard !Task.isCancelled else { return }

if try await !fetchOnePage(folder: folder, direction: .previous) {
break
}
if try await !fetchOnePage(folder: folder, direction: .previous) {
break
}

remainingOldMessagesToFetch -= Constants.pageSize
}
}

remainingOldMessagesToFetch -= Constants.pageSize
private func catchLostOffsetMessageError(folder: Folder, isRetrying: Bool, block: () async throws -> Void) async throws {
do {
try await block()
} catch let error as MailError where error == MailApiError.lostOffsetMessage {
guard !isRetrying else {
DDLogError("We couldn't rebuild folder history even after retrying from scratch")
SentryDebug.failedResetingAfterBackoff(folder: folder)
throw MailError.unknownError
}

DDLogWarn("resetHistoryInfo because of lostOffsetMessageError")
SentryDebug.addResetingFolderBreadcrumb(folder: folder)

await backgroundRealm.execute { realm in
guard let folder = folder.fresh(using: realm) else { return }

try? realm.write {
realm.delete(folder.messages)
realm.delete(folder.threads)
folder.lastUpdate = nil
folder.unreadCount = 0
folder.remainingOldMessagesToFetch = Constants.messageQuantityLimit
folder.isHistoryComplete = false
folder.cursor = nil
}
}
try await messages(folder: folder, isRetrying: true)
}
}

func fetchOnePage(folder: Folder, direction: NewMessagesDirection? = nil) async throws -> Bool {
func messageUidsWithBackOff(folder: Folder,
direction: NewMessagesDirection? = nil,
backoffIndex: Int = 0) async throws -> MessageUidsResult {
let backoffSequence = [1, 1, 2, 8, 34, 144]
guard backoffIndex < backoffSequence.count else {
throw MailError.lostOffsetMessage
}

SentryDebug.addBackoffBreadcrumb(folder: folder, index: backoffIndex)

let realm = getRealm()
var paginationInfo: PaginationInfo?

if let offset = realm.objects(Message.self).where({ $0.folderId == folder.id && $0.fromSearch == false })
.sorted(by: {
let sortedMessages = realm.objects(Message.self).where { $0.folderId == folder.id }
.sorted {
guard let firstMessageShortUid = $0.shortUid,
let secondMessageShortUid = $1.shortUid else {
SentryDebug.castToShortUidFailed(firstUid: $0.uid, secondUid: $1.uid)
Expand All @@ -134,24 +175,52 @@ public extension MailboxManager {
return firstMessageShortUid > secondMessageShortUid
}
return firstMessageShortUid < secondMessageShortUid
}).first?.shortUid?.toString(),
let direction {
}

let backoffOffset = backoffSequence[backoffIndex] - 1
let currentOffset = min(backoffOffset, sortedMessages.count - 1)

// We already did one call and last call was already above sortedMessages.count so we stop wasting more calls
if backoffIndex > 0 && backoffSequence[backoffIndex - 1] - 1 > sortedMessages.count - 1 {
throw MailError.lostOffsetMessage
}

if currentOffset >= 0,
let offset = sortedMessages[currentOffset].shortUid?.toString(),
let direction {
paginationInfo = PaginationInfo(offsetUid: offset, direction: direction)
}

let messageUidsResult = try await apiFetcher.messagesUids(
mailboxUuid: mailbox.uuid,
folderId: folder.id,
paginationInfo: paginationInfo
)
do {
let result = try await apiFetcher.messagesUids(
mailboxUuid: mailbox.uuid,
folderId: folder.id,
paginationInfo: paginationInfo
)
return result
} catch let error as MailError where error == MailApiError.apiMessageNotFound {
try await Task.sleep(nanoseconds: UInt64(0.5 * Double(NSEC_PER_SEC)))
let result = try await messageUidsWithBackOff(
folder: folder,
direction: direction,
backoffIndex: backoffIndex + 1
)

return result
}
}

func fetchOnePage(folder: Folder, direction: NewMessagesDirection? = nil) async throws -> Bool {
let messageUidsResult = try await messageUidsWithBackOff(folder: folder, direction: direction)

let messagesUids = MessagesUids(
addedShortUids: messageUidsResult.messageShortUids,
cursor: messageUidsResult.cursor
)

try await handleMessagesUids(messageUids: messagesUids, folder: folder)

switch paginationInfo?.direction {
switch direction {
case .previous:
return await backgroundRealm.execute { realm in
let freshFolder = folder.fresh(using: realm)
Expand Down Expand Up @@ -481,27 +550,7 @@ public extension MailboxManager {
try await refreshFolder(from: messages)

// TODO: Remove after fix
Task {
for message in messages {
if let liveMessage = message.thaw(),
liveMessage.seen != seen {
SentrySDK.capture(message: "Found incoherent message update") { scope in
scope.setContext(value: ["Message": ["uid": message.uid,
"messageId": message.messageId,
"date": message.date,
"seen": message.seen,
"duplicates": message.duplicates.compactMap(\.messageId),
"references": message.references],
"Seen": ["Expected": seen, "Actual": liveMessage.seen],
"Folder": ["id": message.folder?._id,
"name": message.folder?.name,
"last update": message.folder?.lastUpdate,
"cursor": message.folder?.cursor]],
key: "Message context")
}
}
}
}
SentryDebug.listIncoherentMessageUpdate(messages: messages, actualSeen: seen)
}

/// Set starred the given messages.
Expand Down
47 changes: 47 additions & 0 deletions MailCore/Utils/SentryDebug.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,51 @@ public enum SentryDebug {
data: ["uid": uid])
SentrySDK.addBreadcrumb(breadcrumb)
}

static func listIncoherentMessageUpdate(messages: [Message], actualSeen: Bool) {
Task {
for message in messages {
guard let liveMessage = message.thaw(),
liveMessage.seen != actualSeen else { continue }

SentrySDK.capture(message: "Found incoherent message update") { scope in
scope.setContext(value: ["Message": ["uid": message.uid,
"messageId": message.messageId ?? "nil",
"date": message.date,
"seen": message.seen,
"duplicates": message.duplicates.compactMap(\.messageId),
"references": message.references ?? "nil"],
"Seen": ["Expected": actualSeen, "Actual": liveMessage.seen],
"Folder": ["id": message.folder?._id ?? "nil",
"name": message.folder?.name ?? "nil",
"last update": message.folder?.lastUpdate,
"cursor": message.folder?.cursor ?? "nil"]],
key: "Message context")
}
}
}
}

static func addBackoffBreadcrumb(folder: Folder, index: Int) {
let breadcrumb = Breadcrumb()
breadcrumb.message = "Backoff \(index) for folder \(folder.name) - \(folder.id)"
breadcrumb.level = .warning
breadcrumb.type = "debug"
SentrySDK.addBreadcrumb(breadcrumb)
}

static func addResetingFolderBreadcrumb(folder: Folder) {
let breadcrumb = Breadcrumb()
breadcrumb.message = "Reseting folder after failed backoff \(folder.name) - \(folder.id)"
breadcrumb.level = .warning
breadcrumb.type = "debug"
SentrySDK.addBreadcrumb(breadcrumb)
}

static func failedResetingAfterBackoff(folder: Folder) {
SentrySDK.capture(message: "Failed reseting folder after backoff") { scope in
scope.setContext(value: ["Folder": ["Id": folder.id, "name": folder.name]],
key: "Folder context")
}
}
}
2 changes: 1 addition & 1 deletion Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let project = Project(name: "Mail",
.package(url: "https://github.com/Infomaniak/ios-dependency-injection", .upToNextMajor(from: "1.1.6")),
.package(
url: "https://github.com/Infomaniak/ios-core",
.revision("025c5dffa801d0ffe4e55730897466784f42564f")
.revision("1c741dfd8096fcd0c83200d958917d246ad9e6f2")
),
.package(url: "https://github.com/Infomaniak/ios-core-ui", .upToNextMajor(from: "2.5.3")),
.package(url: "https://github.com/Infomaniak/ios-notifications", .upToNextMajor(from: "3.0.0")),
Expand Down

0 comments on commit 3ca6960

Please sign in to comment.