Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash: NSInternalInconsistencyException - Invalid rowCache row is nil

I'm having an issue with CoreData concurrency on iOS 8.1.

I'm getting the following stack trace for the crash:

NSInternalInconsistencyException - Invalid rowCache row is nil

0     CoreFoundation                        0x0000000183b6659c __exceptionPreprocess + 132
1     libobjc.A.dylib                       0x00000001942640e4 objc_exception_throw + 56
2     CoreData                              0x000000018385b8b8 -[NSSQLCore _newRowCacheRowForToManyUpdatesForRelationship:rowCacheOriginal:originalSnapshot:value:added:deleted:sourceRowPK:properties:sourceObject:newIndexes:reorderedIndexes:] + 6668
3     CoreData                              0x00000001838fbea0 -[NSSQLCore recordToManyChangesForObject:inRow:usingTimestamp:inserted:] + 2604
4     CoreData                              0x0000000183857638 -[NSSQLCore prepareForSave:] + 1052
5     CoreData                              0x00000001838569b4 -[NSSQLCore saveChanges:] + 520
6     CoreData                              0x000000018381f078 -[NSSQLCore executeRequest:withContext:error:] + 716
7     CoreData                              0x00000001838e6254 __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 4048
8     CoreData                              0x00000001838ed654 gutsOfBlockToNSPersistentStoreCoordinatorPerform + 176
9     libdispatch.dylib                     0x00000001948a936c _dispatch_client_callout + 12
10   libdispatch.dylib                      0x00000001948b26e8 _dispatch_barrier_sync_f_invoke + 72
11   CoreData                               0x00000001838e0cb4 _perform + 176
12   CoreData                               0x000000018381ec34 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 296
13   CoreData                               0x0000000183845400 -[NSManagedObjectContext save:] + 1280

This crash happens in several places (about 20), but is most prominent in my data import function which imports 1000s of records and saves every 100.

A few notes about my CoreData setup:

  • I'm using the "Stack #3" from this blog post
  • All of my managed object updating/saving code is done on a performBlock:
  • The crashes are all on the background context

The code where all of these saves are occurring has the following basic pattern:

[backgroundContext performBlock:^{
    ...
    NSArray *result = [backgroundContext fetch...];
    ...

    if ([backgroundContext save:&error]) { // <-- App is crashing here

    }
}];

As far as I understand, there shouldn't be any concurrency issues with the NSPersistentStore behind the backgroundContext but that's what the crash is telling me.

In addition, this only occurs for less than 0.02% of my user base. It's pretty rare (I'm unable to reproduce) but users are not able to recover from this without deleting and reinstalling the app. It will consistently open and crash for them.

Note, this is only for 64-bit iOS 8.1.X - iOS 7.X and 8.0.X do not exhibit this behavior and I don't see it on anything older than an iPhone 5s.


Clarification: deleting the persistent store fixes this issue for all users. This would seem to indicate it is not a concurrency issue.


Creation of Store & Contexts.

[_persistenStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                     configuration:nil
                                               URL:[DatabaseManager storeURL]
                                           options:@{NSMigratePersistentStoresAutomaticallyOption:@YES,
                                                     NSInferMappingModelAutomaticallyOption:@YES}
                                             error:&error];

Creating Contexts

_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_backgroundContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[_backgroundContext setPersistentStoreCoordinator:_persistenStoreCoordinator];
if ([_backgroundContext respondsToSelector:@selector(setName:)]) {
    [_backgroundContext setName:@"DatabaseManager.BackgroundQueue"];
}

_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[_mainContext setPersistentStoreCoordinator:_persistenStoreCoordinator];
if ([_mainContext respondsToSelector:@selector(setName:)]) {
    [_mainContext setName:@"DatabaseManager.MainQueue"];
}

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(mainContextDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:_mainContext];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(backgroundContextDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:_backgroundContext];

Listening for changes

- (void) mainContextDidSave:(NSNotification *)notification
{
    [_backgroundContext performBlock:^{
        [self->_backgroundContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (void) backgroundContextDidSave:(NSNotification*)notification
{
    [_mainContext performBlock:^{
        NSArray* updated = [notification.userInfo valueForKey:NSUpdatedObjectsKey];

        // Fault all objects that will be updated.
        for (NSManagedObject* obj in updated) {
            NSManagedObject* mainThreadObject = [self->_mainContext objectWithID:obj.objectID];
            [mainThreadObject willAccessValueForKey:nil];
        }

        [self->_mainContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

How code is executed on each of the contexts. I pass a block into here:

- (void)executeBackgroundOperation:(void (^)(NSManagedObjectContext *))operation {

    NSAssert(operation, @"No Background operation to perform");

    [_backgroundContext performBlock:^{
        operation(self->_backgroundContext);
    }];
}

- (void)executeMainThreadOperation:(void (^)(NSManagedObjectContext *))operation {
    NSAssert(operation, @"No Main Thread operation to perform");

    [_mainContext performBlock:^{
        operation(self->_mainContext);
    }];
}

The only other threads in the crash report that have a lock in them are two Javascript threads that look like this:

0   libsystem_kernel.dylib 0x3a77cb38 __psynch_cvwait + 24
1   libsystem_pthread.dylib 0x3a7fa2dd pthread_cond_wait + 38
2   libc++.1.dylib 0x39a11e91 _ZNSt3__118condition_variable4waitERNS_11unique_lockINS_5mutexEEE + 34
3   JavaScriptCore 0x2dcd4cb5 _ZN3JSC8GCThread16waitForNextPhaseEv + 102
4   JavaScriptCore 0x2dcd4d19 _ZN3JSC8GCThread12gcThreadMainEv + 50
5   JavaScriptCore 0x2db09597 _ZN3WTFL19wtfThreadEntryPointEPv + 12
6   libsystem_pthread.dylib 0x3a7f9e93 _pthread_body + 136
7   libsystem_pthread.dylib 0x3a7f9e07 _pthread_start + 116
8   libsystem_pthread.dylib 0x3a7f7b90 thread_start + 6
like image 540
Stephen Furlani Avatar asked Feb 11 '15 20:02

Stephen Furlani


1 Answers

You are correct in thinking this may not be a concurrency issue. Deleting and installing the app isually fixes core data errors when the migration hasn't been done properly.

If you released the app, changed the core data model, then released an update (without migration), this may be the source of your crash.

Follow this tutorial and see if it fixes your issue. http://www.raywenderlich.com/27657/how-to-perform-a-lightweight-core-data-migration

like image 105
William Falcon Avatar answered Nov 11 '22 07:11

William Falcon