Skip to content

Commit

Permalink
Merge pull request #824 from Infomaniak/debug-wrong-thread-date
Browse files Browse the repository at this point in the history
feat: Debug wrong thread date
  • Loading branch information
Ambrdctr committed Jun 21, 2023
2 parents 29e77c9 + 2f15ed6 commit 3c0b0b8
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 28 deletions.
4 changes: 3 additions & 1 deletion Mail/Helpers/PreviewHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import MailCore
import RealmSwift
import SwiftUI

struct PreviewHelper {
#if DEBUG
enum PreviewHelper {
static let sampleMailboxManager = MailboxManager(mailbox: sampleMailbox, apiFetcher: MailApiFetcher())

static let sampleMailbox = Mailbox(uuid: "",
Expand Down Expand Up @@ -132,3 +133,4 @@ struct PreviewHelper {
expirationDate: Date()
))
}
#endif
49 changes: 30 additions & 19 deletions MailCore/Cache/MailboxManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public class MailboxManager: ObservableObject {
let realmName = "\(mailbox.userId)-\(mailbox.mailboxId).realm"
realmConfiguration = Realm.Configuration(
fileURL: MailboxManager.constants.rootDocumentsURL.appendingPathComponent(realmName),
schemaVersion: 11,
schemaVersion: 12,
deleteRealmIfMigrationNeeded: true,
objectTypes: [
Folder.self,
Expand Down Expand Up @@ -268,7 +268,7 @@ public class MailboxManager: ObservableObject {
}
}

private func deleteMessages(uids: [String], folder: Folder) async {
private func deleteMessages(uids: [String]) async {
guard !uids.isEmpty && !Task.isCancelled else { return }

await backgroundRealm.execute { realm in
Expand Down Expand Up @@ -304,14 +304,7 @@ public class MailboxManager: ObservableObject {
try thread.recomputeOrFail()
} catch {
threadsToDelete.insert(thread)
SentrySDK.capture(message: "Thread has nil lastMessageFromFolderDate") { scope in
scope.setContext(value: ["dates": "\(thread.messages.map { $0.date })",
"ids": "\(thread.messages.map { $0.id })"],
key: "all messages")
scope.setContext(value: ["id": "\(thread.lastMessageFromFolder?.uid ?? "nil")"],
key: "lastMessageFromFolder")
scope.setContext(value: ["date before error": thread.date], key: "thread")
}
SentryDebug.threadHasNilLastMessageFromFolderDate(thread: thread)
}
}
}
Expand Down Expand Up @@ -768,9 +761,34 @@ public class MailboxManager: ObservableObject {
}

private func handleMessagesUids(messageUids: MessagesUids, folder: Folder) async throws {
await deleteMessages(uids: messageUids.deletedUids, folder: folder)
let alreadyWrongIds = folder.fresh(using: getRealm())?.threads
.where { $0.date == SentryDebug.knownDebugDate }
.map { $0.uid } ?? []
await deleteMessages(uids: messageUids.deletedUids)
var shouldIgnoreNextEvents = SentryDebug.captureWrongDate(
step: "After delete",
folder: folder,
alreadyWrongIds: alreadyWrongIds,
realm: getRealm()
)
await updateMessages(updates: messageUids.updated, folder: folder)
if !shouldIgnoreNextEvents {
shouldIgnoreNextEvents = SentryDebug.captureWrongDate(
step: "After updateMessages",
folder: folder,
alreadyWrongIds: alreadyWrongIds,
realm: getRealm()
)
}
try await addMessages(shortUids: messageUids.addedShortUids, folder: folder, newCursor: messageUids.cursor)
if !shouldIgnoreNextEvents {
_ = SentryDebug.captureWrongDate(
step: "After addMessages",
folder: folder,
alreadyWrongIds: alreadyWrongIds,
realm: getRealm()
)
}
}

private func addMessages(shortUids: [String], folder: Folder, newCursor: String?) async throws {
Expand Down Expand Up @@ -899,14 +917,7 @@ public class MailboxManager: ObservableObject {
do {
try thread.recomputeOrFail()
} catch {
SentrySDK.capture(message: "Thread has nil lastMessageFromFolderDate") { scope in
scope.setContext(value: ["dates": "\(thread.messages.map { $0.date })",
"ids": "\(thread.messages.map { $0.id })"],
key: "all messages")
scope.setContext(value: ["id": "\(thread.lastMessageFromFolder?.uid ?? "nil")"],
key: "lastMessageFromFolder")
scope.setContext(value: ["date before error": thread.date], key: "thread")
}
SentryDebug.threadHasNilLastMessageFromFolderDate(thread: thread)
realm.delete(thread)
}
}
Expand Down
24 changes: 18 additions & 6 deletions MailCore/Models/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import MailResources
import RealmSwift
import Sentry

// TODO move to core
// TODO: move to core
public extension String {
/// Max length of a string before we need to truncate it.
static let closeToMaxRealmSize = 14_000_000
Expand All @@ -33,7 +33,7 @@ public extension String {
var truncatedForRealmIfNeeded: Self {
Self.truncatedForRealmIfNeeded(self)
}

/// Truncate a string for compatibility with Realm if needed
///
/// The string will be terminated by " [truncated]" if it was
Expand Down Expand Up @@ -303,14 +303,27 @@ public final class Message: Object, Decodable, Identifiable {

public required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
uid = try values.decode(String.self, forKey: .uid)
let uid = try values.decode(String.self, forKey: .uid)
self.uid = uid
if let msgId = try? values.decode(String.self, forKey: .messageId) {
messageId = msgId
linkedUids = [msgId].toRealmSet()
}
subject = try values.decodeIfPresent(String.self, forKey: .subject)
priority = try values.decode(MessagePriority.self, forKey: .priority)
date = (try? values.decode(Date.self, forKey: .date)) ?? Date()
if let date = (try? values.decode(Date.self, forKey: .date)) {
self.date = date
} else {
// FIXME: Remove after thread date bug fix
date = SentryDebug.knownDebugDate
SentrySDK
.addBreadcrumb(SentryDebug.createBreadcrumb(
level: .warning,
category: "Thread algo",
message: "Nil message date decoded",
data: ["uid": uid]
))
}
size = try values.decode(Int.self, forKey: .size)
from = try values.decode(List<Recipient>.self, forKey: .from)
to = try values.decode(List<Recipient>.self, forKey: .to)
Expand Down Expand Up @@ -454,10 +467,9 @@ final class ProxyBody: Codable {

/// Generate a new persisted realm object on the fly
public func realmObject() -> Body {

// truncate message if needed
let truncatedValue = value?.truncatedForRealmIfNeeded

let body = Body()
body.value = truncatedValue
body.type = type
Expand Down
2 changes: 1 addition & 1 deletion MailCore/Models/Thread.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class Thread: Object, Decodable, Identifiable {
@Persisted public var cc: List<Recipient>
@Persisted public var bcc: List<Recipient>
@Persisted public var subject: String?
@Persisted public var date: Date
@Persisted(indexed: true) public var date: Date
@Persisted public var hasAttachments: Bool
@Persisted public var hasSwissTransferAttachments: Bool
@Persisted public var hasDrafts: Bool
Expand Down
48 changes: 47 additions & 1 deletion MailCore/Utils/SentryDebug.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import Foundation
import RealmSwift
import Sentry

struct SentryDebug {
enum SentryDebug {
static let knownDebugDate = Date(timeIntervalSince1970: 1_893_456_000)
static func sendMissingMessagesSentry(sentUids: [String], receivedMessages: [Message], folder: Folder, newCursor: String?) {
if receivedMessages.count != sentUids.count {
let receivedUids = Set(receivedMessages.map { Constants.shortUid(from: $0.uid) })
Expand Down Expand Up @@ -72,4 +73,49 @@ struct SentryDebug {
}
}
}

static func threadHasNilLastMessageFromFolderDate(thread: Thread) {
SentrySDK.capture(message: "Thread has nil lastMessageFromFolderDate") { scope in
scope.setContext(value: ["dates": "\(thread.messages.map { $0.date })",
"ids": "\(thread.messages.map { $0.id })"],
key: "all messages")
scope.setContext(value: ["id": "\(thread.lastMessageFromFolder?.uid ?? "nil")"],
key: "lastMessageFromFolder")
scope.setContext(value: ["date before error": thread.date], key: "thread")
}
}

static func createBreadcrumb(level: SentryLevel,
category: String,
message: String,
data: [String: Any]? = nil) -> Breadcrumb {
let crumb = Breadcrumb(level: level, category: category)
crumb.type = level == .info ? "info" : "error"
crumb.message = message
crumb.data = data
return crumb
}

static func captureWrongDate(step: String, folder: Folder, alreadyWrongIds: [String], realm: Realm) -> Bool {
guard let freshFolder = folder.fresh(using: realm) else { return false }

let threads = freshFolder.threads.where { $0.date == knownDebugDate }.filter { !alreadyWrongIds.contains($0.uid) }
guard !threads.isEmpty else { return false }

SentrySDK.capture(message: "Threads with wrong date on step \(step)") { scope in
scope.setLevel(.error)
scope.setContext(value: ["threads": Array(threads).map {
[
"uid": "\($0.uid)",
"subject": $0.subject ?? "No subject",
"messageIds": "\($0.messageIds.joined(separator: ","))",
"lastMessageFromFolder": $0.lastMessageFromFolder?.uid ?? "nil",
"messages": Array($0.messages)
.map { ["message uid": $0.uid, "message subject": $0.subject ?? "No subject", "message date": $0.date] }
]
}],
key: "threads")
}
return true
}
}

0 comments on commit 3c0b0b8

Please sign in to comment.