realm / realm-core

Core database component for the Realm Mobile Database SDKs
https://realm.io
Apache License 2.0
1.01k stars 158 forks source link

Key 'Number' not found in '<Class Name>' when nullifying incoming links #7717

Closed jhoanarango closed 2 hours ago

jhoanarango commented 4 months ago

How frequently does the bug occur?

Sometimes

Description

The 'Crashlytics' from Firebase is showing a crash on my project which is happening to a few users. I have not been able to reproduce this myself.

I have an Object that takes care of handling all Realm operations such as saving and getting data. I use this object to save an object I call "EventTracker". EventTracker has a list of 'TrackedEvent' which it has 2 properties, an event ID and an ID, both of type strings. These keep track of events the user has with their calendar and their are added/deleted depending on the user downloaded content.

It's been hard to find what is causing this crash since I've done all kinds of modifications on my code and they come back with the same outcome.

Any idea as to what may cause this error?

Stacktrace & log output

Fatal Exception: RLMException
0  CoreFoundation                 0xecb28 __exceptionPreprocess
1  libobjc.A.dylib                0x2af78 objc_exception_throw
2  RealmSwift                     0x53c83c RLMThrowCollectionException(NSString*) + 101 (RLMResults.mm:101)
3  RealmSwift                     0x53e93c -[RLMResults deleteObjectsFromRealm] + 63 (RLMResults_Private.hpp:63)
4  RealmSwift                     0x535ef0 -[RLMRealm deleteObjects:] + 911 (RLMRealm.mm:911)
5  Crewline                       0x2d3c34 closure realm/realm-swift#2 in static RealmManager.saveLive(_:)
6  Crewline                       0x2e65e0 partial apply for closure realm/realm-swift#3 in static RealmManager.saveEventTracker(_:) (<compiler-generated>)
7  Crewline                       0x2dd570 partial apply for thunk for @callee_guaranteed () -> (@error @owned Error) (<compiler-generated>)
8  Crewline                       0x2e6a30 thunk for @callee_guaranteed () -> (@error @owned Error)partial apply
9  RealmSwift                     0x788dc Realm.write<A>(withoutNotifying:_:) + 265 (Realm.swift:265)
10 Crewline                       0x2d1674 static RealmManager.saveEventTracker(_:) + 78 (Extension-Realm.swift:78)
11 Crewline                       0x16c5e4 closure realm/realm-swift#1 in closure realm/realm-swift#1 in CalendarCollectionViewCell.downloadSchedule()
12 Crewline                       0x26c754 thunk for @escaping @callee_guaranteed @Sendable () -> () (<compiler-generated>)
13 libdispatch.dylib              0x213c _dispatch_call_block_and_release
14 libdispatch.dylib              0x3dd4 _dispatch_client_callout
15 libdispatch.dylib              0x125a4 _dispatch_main_queue_drain
16 libdispatch.dylib              0x121b8 _dispatch_main_queue_callback_4CF
17 CoreFoundation                 0x3751c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
18 CoreFoundation                 0x34218 __CFRunLoopRun
19 CoreFoundation                 0x33968 CFRunLoopRunSpecific
20 GraphicsServices               0x34e0 GSEventRunModal
21 UIKitCore                      0x22aedc -[UIApplication _run]
22 UIKitCore                      0x22a518 UIApplicationMain
23 Crewline                       0x310dc0 main + 17 (AppDelegate.swift:17)
24 ???                            0x1c83c2d84 (Missing)

Can you reproduce the bug?

No

Reproduction Steps

No response

Version

10.50.1

What Atlas Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

iOS

Build environment

Xcode version: 15.1 Dependency manager and version: SPM

sync-by-unito[bot] commented 4 months ago

➤ PM Bot commented:

Jira ticket: RCORE-2133

nirinchev commented 4 months ago

Transferring to Core repo as this is an exception thrown deep in Core.

jhoanarango commented 3 months ago

Anything on this? This bug is really affecting my app and my user's experience.

chenyanping01 commented 3 months ago

Our app is also greatly affected. Each version has thousands of crashes. What are the common causes of such crashes? Thanks a lot.

jhoanarango commented 3 months ago

Our app is also greatly affected. Each version has thousands of crashes. What are the common causes of such crashes? Thanks a lot.

I think it would help if you post your stacktrace & log output from Firebase showing you the moment of the crash. We may be able to compare and allow the developers to have more evidence and point them to the right direction.

jhoanarango commented 3 months ago

@nirinchev anyone that we can tag into this issue? It's been sitting here with no response. Thanks a lot.

ironage commented 3 months ago

Hi @jhoanarango thanks for reporting this. Are you able to share any more information to help us track this down?

Internal note: realm-swift 10.50.1 uses core v14.6.2.

chenyanping01 commented 3 months ago
// Delete expired messages when the App starts
func delete(msg: Message) {
    realm.delete(msg._to)
    realm.delete(msg)
}
//Here is my scheme of the objects implementation:
open class Message:Object {
    @objc dynamic var pId = ""
    @objc dynamic var folderId: String? = nil
    @objc dynamic var date = Date.distantPast
    @objc dynamic var flag = 0
    @objc dynamic var hasAttachment = false
    @objc dynamic var from:ContactItem?

    var authResults = List<String>();
    private var _to = List<ContactItem>()
}
//embedded objects
open class ContactItem: Object{
    @objc dynamic var pId = ""
    @objc dynamic var name: String? = ""
    @objc dynamic var isRobot = false
    @objc dynamic var lastUpdated:Date = Date.distantPast

    private let _messages = LinkingObjects(fromType: Message.self, property: "from")
}

Is it because LinkingObject is used in embedded objects?

jedelbo commented 3 months ago

Seems like you are trying to delete an object that is already deleted.

chenyanping01 commented 3 months ago

I have checked whether the Object is invalidate before deleting it. Does this check work? Thanks a lot.

 // Here is the delete function:
 func delete<T: Object>(_ entity: T) {
        if entity.isInvalidated {
            return
        }
        autoreleasepool {
            do {
                if realm.isInWriteTransaction {
                    realm.delete(entity)
                } else {
                    try realm.write {
                        realm.delete(entity)
                    }
                }
            } catch let error as NSError {

            }
        }
    }
jedelbo commented 3 months ago

I am not sure how the Swift SDK operation translate into core. @nirinchev can you help here?

jhoanarango commented 3 months ago

Hi @jhoanarango thanks for reporting this. Are you able to share any more information to help us track this down?

  • What realm operations are you doing in RealmManager.saveLive?
  • Can you share the schema of the objects being deleted in the transaction created by the RealmManager.saveLive code?
  • The exception has to do with a link relationship, so we are looking for anything unusual in the object graph of the objects being deleted (ex: embedded objects? lists of links with duplicate links? link cycles? etc)

Internal note: realm-swift 10.50.1 uses core v14.6.2.

Thanks for the reply @ironage,

Here are the steps my app is taking. As noted before, I have an object that takes care of all realm operations. This object has some methods that save/queries/modifies the objects handled by Realm.

When a user downloads the information from our servers, it saves what we call "opentime trips" and it also creates and saves "Tracked Events". Events are calendar events from the user's calendar and they have a unique ID that must be tracked in order for us to make any changes or delete those events. These operations are handled once the user downloads data from our servers. The tracked events function happens first and then is followed by saving the opentime trips. Opentime trips are also delete or added depending on the new data download from the servers. For example, if an opentime trip is no longer on our servers and the user download the new data, then the app will filter out those trips that are no longer on the list and deletes them. The same operation is being done for the calendar events.

Saving Opentime Trips

Opentime Schema:


class OpenTime: Object, Decodable {

    // MARK: - Properties

    @Persisted var opentimeTrips : List<OpenTimeTrip>

    // Primary Key

    @Persisted(primaryKey: true)
    var month: String = "0326"

}

OpenTimeTrip Schema:


class OpenTimeTrip: Object, Decodable {

    // MARK: - Properties

    @Persisted var DATE           : String = ""
    @Persisted var arrive         : String = ""
    @Persisted var blkHours       : String = ""
    @Persisted var credit         : String = ""
    @Persisted var dates          : String = ""
    @Persisted var days           : Int?
    @Persisted var depart         : String = ""
    @Persisted var isTb           : Int?
    @Persisted var layover        : String = ""
    @Persisted var pairing        : String = ""
    @Persisted var report         : String = ""
    @Persisted var isFavorite     : Bool   = false
    @Persisted var isDislike      : Bool   = false
    @Persisted var isPending      : Bool   = false
    @Persisted var status         : String = ""
    @Persisted var belongsTo      : String = ""
    @Persisted var isBlocked      : Int    = 0
    @Persisted var tripWorth      : String? = "" // Used to show the worth for the scheduled pairing on the OpenTimeViewController
    @Persisted var position       : List<String>
    @Persisted var forSwap        : Bool  = false
    @Persisted var isDisqualified : Bool? = false
    @Persisted var isPremium      : Bool? = false
    @Persisted var timeInOpentime : String? = ""
    @Persisted var disqualifiedMessage: String? = ""
    @Persisted var isSplit        : Bool? = false
    @Persisted var splitLegs      : List<Int>

    // Primary Key

    @Persisted(primaryKey: true)
    var id: String = ""

    //  #### Other code

}

RealmManager/saveLive


    static func saveLive(_ openTime: [OpenTime]) {
        setRealm()

        guard let openTime = openTime.first else { return }
        let openTimeTripIds: [String] = openTime.opentimeTrips.map { $0.id }

        if let tripsInRealm = realm?.objects(OpenTimeTrip.self).filter("belongsTo = '\(openTime.month)'") {
            let tripsToDelete = tripsInRealm.filter("NOT id IN %@", openTimeTripIds)

            do {
                try realm?.safeWrite {
                    realm?.delete(tripsToDelete)
                }
            } catch {
                print("Error deleting OpenTime Trips in Realm")
            }
        }

        do {
            try realm?.safeWrite {

                 /// Keeps the favorite trips from open time if they are still available
                 if let favoriteTrips = self.getFavoriteTrips() {
                     for trip in openTime.opentimeTrips {
                         for favoriteTrip in favoriteTrips where trip.DATE == favoriteTrip.DATE && trip.pairing == favoriteTrip.pairing {
                             trip.isFavorite = favoriteTrip.isFavorite
                         }
                     }
                 }

                save([openTime])
            }
        } catch {

        }
    }

Event Tracker


class EventTracker: Object {

    // MARK: - Properties

    @Persisted var trackedEvents: List<TrackedEvent>

    /// Primary Key
    @Persisted(primaryKey: true)
    var month: String = ""

    // MARK: - Life Cycle

    init(month: String, trackedEvents: [TrackedEvent]) {
        self.month   = month
        let trackedEventsToSave = List<TrackedEvent>()
        trackedEventsToSave.append(objectsIn: trackedEvents)

        self.trackedEvents = trackedEventsToSave

        super.init()
    }

    required override init() {
        super.init()
    }
}

Tracked Event


class TrackedEvent: Object {

    // MARK: - Properties

    @Persisted
    var eventID: String

    // Primary Key
    @Persisted(primaryKey: true)
    var id: String = ""

    // MARK: - Life Cycle

    init(id: String, eventID: String) {
        self.id = id
        self.eventID = eventID

        super.init()
    }

    required override init() {
        super.init()
    }
}

RealmManager/saveEventTracker


    static func saveEventTracker(_ tracker: EventTracker) -> [String]? {
        setRealm()
        var eventIdsToDelete: [String] = []

        var trackedEventList: [TrackedEvent] = []
        trackedEventList.append(contentsOf: tracker.trackedEvents)

        let trackedEventIds = trackedEventList.map { $0.id }

        if let eventTracker = realm?.objects(EventTracker.self).filter("month = '\(tracker.month)'").first {
            let trackEventsToDelete = eventTracker.trackedEvents.filter("NOT id IN %@", trackedEventIds)
            eventIdsToDelete = trackEventsToDelete.map { $0.eventID } as [String] /// The event ID's that will be returned so that we can remove the event.

            do {
                try realm?.safeWrite {
                    realm?.delete(trackEventsToDelete)
                }
            } catch {
                print("Catch Error in \(#file), line \(#line)")
            }
        }

        self.save([tracker])
        return eventIdsToDelete
    }
jhoanarango commented 3 months ago

@ironage, I would like to also note that, this issue started happening after I updated Realm at some point.

Also, the "EventTracker" is a feature that is optional to the user. When turned off, the crashing goes away. The saving of the "Opentime" objects is not an option the user can turn off. What I am trying to point out is that the issue to me does not seem to be in the "saveLive", but on the track events. Also, Firebase's crashlytics show the following message as the title for the crash.

Screenshot 2024-06-18 at 1 58 26 PM

Key '2169' not found in 'TrackedEvent' when nullifying incoming links

jhoanarango commented 3 months ago

@ironage

In case you want to know what the safeWrite is..


extension Realm {

    /// A more safe way of writing to disk.
    /// - Parameter block: The block to write on discs
    /// - Throws: Throws an issue if one is found.
     func safeWrite(_ block: (() throws -> Void)) throws {
        if isInWriteTransaction {
            try block()
        } else {
            try write(block)
        }
    }
}
chenyanping01 commented 1 month ago

FYI: I see that recent version of realm-core(Realm Core v14.10.3) has a fix for this issue, Core#7828 and Core#7594 but When I upgraded the Realm SDK to 10.55.2(realm-core v14.11.0) and the issue still exists.

jhoanarango commented 1 month ago

FYI: I see that recent version of realm-core(Realm Core v14.10.3) has a fix for this issue, Core#7828 and Core#7594 but When I upgraded the Realm SDK to 10.55.2(realm-core v14.11.0) and the issue still exists.

I had to remove the realm that handled this process in my app. Too many people having crashes and no real movement on here. Maybe look for an alternative where you are using Realm.

ironage commented 1 month ago

@jhoanarango @chenyanping01 thanks for your feedback. We never did get a repro from this specific issue but I do think the root cause is very likely fixed by https://github.com/realm/realm-core/pull/7830. That being said, a Realm affected by this issue may have been put into an invalid state if your app encountered this issue before the fix. There were assertions present in our code to prevent this from happening, but they were only enabled for debug builds, which you would have had off in a release build. If you are able to provide us with an affected Realm, we could inspect it to determine if this is indeed the case. It would also be useful for us if you can confirm that this exception is not found by any of your new users who started fresh from a version of Realm containing the fix. That being said, you still need a solution for your users who are affected by the invalid backlinks. If you are certain that this is the situation your users are in, one idea would be for you to manually duplicate your objects to another Realm and use that going forward.

chenyanping01 commented 1 month ago

@ironage thanks for the reply.

There were assertions present in our code to prevent this from happening, but they were only enabled for debug builds,

We got this crash issue from Firebase Crashlytics report. We are very sure that this is not only happening in Debug builds, as hundreds of users encounter crashes in each version. Can you help confirm that this assertion is only happening in Debug Builds?

If you are able to provide us with an affected Realm

Do you mean the realm version we used? I'm not sure which version this problem started, probably version 10.40. The version we are currently using is the latest version 10.52.2.

It would also be useful for us if you can confirm that this exception is not found by any of your new users who started fresh from a version of Realm containing the fix.

I can add custom logs to see if this exception is not found by any of your new users. I will feedback later.

one idea would be for you to manually duplicate your objects to another Realm and use that going forward.

It's hard to manually duplicate objects to another Realm and use that going forward. Because the table in the database may be very large, there may be problems with online user migration.

ironage commented 1 month ago

@chenyanping01

We got this crash issue from Firebase Crashlytics report.

Yes, but Key 'x' not found in 'y' when nullifying incoming links is an exception which can be caught. The assertions that are only turned on in debug mode would be something like this array_backlink.cpp:112: Assertion failed: int64_t(value >> 1) == key.value and would cause an abort.

Do you mean the realm version we used?

I mean, if you are able to, please send us the Realm file that is associated with one of these issues. Perhaps we can learn more from inspecting the data.

I can add custom logs

Thank you!

It's hard to manually duplicate objects

Understood. We may be able to make a migration to fix the data in place automatically, but before we go down that path, I would like to be sure that this is actually the case. Right now we are still guessing, because we have not been able to reproduce this issue. Any further information that can help us understand the root cause here would be greatly appreciated!

chenyanping01 commented 1 month ago

Thank you for the reply. @ironage

The assertions that are only turned on in debug mode

Sorry, I'm still confused, if this only turned on in debug mode. Why there are so many crashes in release mode. Did I miss something here.

ironage commented 1 month ago

@chenyanping01 apologies for the confusion, I will try to clarify. The exception reported in this issue is always enabled and that is what you appear to be experiencing. I would not classify it as a hard crash because you can use a try...catch to catch it in your code. The assertions I referred to earlier are not catchable, and those are only enabled in debug mode but you do not seem to be seeing those. Hope that helps clear things up.

As I mentioned earlier, if you are able to provide a reproduction for this we'd greatly appreciate it!

chenyanping01 commented 4 weeks ago

Thanks for clarification. @ironage Through observation of the two versions, this crash currently did not occur for users who newly installed the app. (I'm not 100% sure)

sync-by-unito[bot] commented 2 hours ago

➤ jedelbo commented:

Based on the comments, I assume this has been fixed