drewmccormack / ensembles

A synchronization framework for Core Data.
MIT License
1.63k stars 132 forks source link

Crash: [NSNull globalIdentifier] #220

Closed pronebird closed 7 years ago

pronebird commented 8 years ago

Debugger stopped today in CDEEventBuilder:

- (void)convertToOneRelationshipValuesToGlobalIdentifiersInPropertyChangeValue:(CDEPropertyChangeValue *)propertyChange withGlobalIdentifiersByObjectID:(NSDictionary *)globalIdentifiersByObjectID
{
    CDEGlobalIdentifier *globalId = nil;
    globalId = globalIdentifiersByObjectID[propertyChange.relatedIdentifier];
    if (propertyChange.relatedIdentifier && !globalId) {
        CDELog(CDELoggingLevelError, @"No global id found for to-one relationship with target objectID: %@", propertyChange.relatedIdentifier);
    }
    propertyChange.relatedIdentifier = globalId.globalIdentifier; // <--- HERE
}

It looked like I've got an exception or something, or maybe Xcode is under weather. Says globalId is NSNull.

Printing description of globalIdentifiersByObjectID:
{
    "0xd0000000000c0020 <x-coredata://8AFDDF3F-D70F-4A99-A022-A522EF526ED9/EntityA/p3>" = "<null>";
    "0xd0000000001c0024 <x-coredata://8AFDDF3F-D70F-4A99-A022-A522EF526ED9/EntityB/p7>" = "<null>";
}

This is the code that I use to return global IDs:

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects {
    return [objects valueForKeyPath:@"uniqueIdentifier"];
}

and then app enters foreground and I get this:

2016-03-08 12:30:24.127 App[2323:1141855] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull globalIdentifier]: unrecognized selector sent to instance 0x1a0d4cea8'
*** First throw call stack:
(0x182c65900 0x1822d3f80 0x182c6c61c 0x182c695b8 0x182b6d68c 0x100867b18 0x10086789c 0x1008676a4 0x100862b3c 0x1008893c8 0x1008629b8 0x184765080 0x184764f48 0x100862804 0x1008bf7a4 0x184765080 0x100c5dbb0 0x100c6a6c8 0x100c618a0 0x100c5dbb0 0x100c6ce10 0x100c6c4d8 0x1828cd470 0x1828cd020)
libc++abi.dylib: terminating with uncaught exception of type NSException

Each object I have in my Model implements "uniqueIdentifier" key. Any clues why that happens?`

drewmccormack commented 8 years ago

Things to check:

pronebird commented 8 years ago

@drewmccormack I certainly can put a check for NSNulls before returning global identifiers.

Each entity implements awakeFromInsert as suggested in your book:

- (void)awakeFromInsert {
    [super awakeFromInsert];

    if(!self.uniqueIdentifier) {
        self.uniqueIdentifier = [[NSUUID UUID] UUIDString];
    }
}

Seed database that I migrate on first run has some pre-populated data but to my understanding NSUUID should guarantee uniqueness anyway.

drewmccormack commented 8 years ago

Can you say anything about the relationship that caused it? Is it perhaps to a parent entity?

I guess this Ensembles v1, right? Or is it v2?

Kind regards, Drew

On 08 Mar 2016, at 16:11, Andrey Mikhaylov notifications@github.com wrote:

@drewmccormack I certainly can put a check for NSNulls before returning global identifiers.

Each entity implements awakeFromInsert as suggested in your book:

  • (void)awakeFromInsert { [super awakeFromInsert];

    if(!self.uniqueIdentifier) { self.uniqueIdentifier = [[NSUUID UUID] UUIDString]; } } Seed database that I migrate on first run has some pre-populated data but to my understanding NSUUID should guarantee uniqueness anyway.

— Reply to this email directly or view it on GitHub.

pronebird commented 8 years ago

Yes this is Ensembles v1. Relationship is one-to-one between entity A -> B with reverse one to many, so B refers to many A, while each A can only be contained within single B. I don't use parent entities and have entirely flat hierarchy.

drewmccormack commented 8 years ago

Very odd. Anything else you can say about when it happened? What had just happened?

Otherwise it is difficult to say what could be going on.

Kind regards, Drew

On 08 Mar 2016, at 16:24, Andrey Mikhaylov notifications@github.com wrote:

Yes this is Ensembles v1. Relationship is one-to-one between entity A -> B with reverse one to many, so B refers to many A, while each A can only be contained within single B. I don't use parent entities and have entirely flat hierarchy.

— Reply to this email directly or view it on GitHub.

pronebird commented 8 years ago

So I just added new object with all relationships like usual and saved it. I am trying to figure out if I can put any diagnostics in Ensembles to see any potential issues. This bug happens once in a while and does not seem to have any consequences. When I restart the app everything is fine and my records are not lost.

drewmccormack commented 8 years ago

Do you use any extra contexts? Do you use a background context for saving?

Kind regards, Drew

On 08 Mar 2016, at 16:27, Andrey Mikhaylov notifications@github.com wrote:

So I just added new object with all relationships like usual and saved it. I am trying to figure out if I can put any diagnostics in Ensembles to see if anything caused the problem. This bug happens once in a while and does not seem to have any consequences. When I restart the app everything is fine and my records are not lost.

— Reply to this email directly or view it on GitHub.

pronebird commented 8 years ago

Yes, I have root saving context running on private queue and main queue context connected to it. I merge changes to main context when private queue is saved. I also have couple of managers that monitor save events on both root context and ensembles and update certain entities in object graph if needed.

pronebird commented 8 years ago

I've added check for NSNull. Will let you know if I spot anything

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects {
    NSArray *identifiers = [objects valueForKeyPath:@"uniqueIdentifier"];

    [identifiers enumerateObjectsUsingBlock:^(id identifier, NSUInteger idx, BOOL *stop) {
        NSAssert([[NSNull null] isEqual:identifier] == NO, @"uniqueIdentifier cannot be NSNull. Managed object: %@", objects[idx]);
    }];

    return identifiers;
}
drewmccormack commented 8 years ago

You have to be very careful with a background context, because ensembles only sees the objects when you save to disk. See the book for tips. Saving and merging chapters.

Kind regards, Drew

On 08 Mar 2016, at 16:36, Andrey Mikhaylov notifications@github.com wrote:

I've added check for NSNull. Will let you know if I spot

  • (NSArray )persistentStoreEnsemble:(CDEPersistentStoreEnsemble )ensemble globalIdentifiersForManagedObjects:(NSArray )objects { NSArray identifiers = [objects valueForKeyPath:@"uniqueIdentifier"];

    [identifiers enumerateObjectsUsingBlock:^(id identifier, NSUInteger idx, BOOL *stop) { NSAssert([[NSNull null] isEqual:identifier] == NO, @"uniqueIdentifier cannot be NSNull. Managed object: %@", objects[idx]); }];

    return identifiers; } — Reply to this email directly or view it on GitHub.

pronebird commented 8 years ago

I monitor root context and once it's saved I go over saved objects and index the content (e.g. tokenize), imagine blog posts and search index. Search index is then saved to CoreData too. There is no cycle because I filter out entities and properties of interest. I do the same when changes arrive from Ensembles/iCloud. I'll review my code and check your theory. I hope it's bug on my side and not in Ensembles :)