Stefan Arn
Stefan Arn

Reputation: 1154

Rare crash with multiple child ManagedObjectContexts and NSManagedObjectContextDidSaveNotification

We get crash reports back from our production app. It crashes about 1% of all app openings and we were never able to get the app to crash during a XCode session or on the simulator. But we were able to reproduce the crash on a device without a XCode session running. I believe that it is a race condition between multiple ManagedObjectContexts that try to get informed about changes with a NSManagedObjectContextDidSaveNotification

But first here is a sample crash report:

Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x6000000c
Crashed Thread:  22

Thread 0:
0   libsystem_kernel.dylib              0x30ba24c4 semaphore_wait_trap + 8
1   libdispatch.dylib                   0x30ad17ff _dispatch_barrier_sync_f_slow + 363
2   CoreData                            0x22452ced _perform + 173
3   CoreData                            0x2245fd9f -[NSManagedObjectContext(_NestedContextSupport) managedObjectContextDidRegisterObjectsWithIDs:] + 67
4   CoreData                            0x223e3467 _PFFaultHandlerLookupRow + 1319
5   CoreData                            0x223e2bd1 _PF_FulfillDeferredFault + 233
6   CoreData                            0x223e2a35 _sharedIMPL_pvfk_core + 61
7   myApp                               0x0014bc11 -[E5ServiceEndpointController serviceEndpointUrlForKey:withHost:andContext:] (E5ServiceEndpointController.m:116)
8   myApp                               0x0014b3bd -[E5ServiceEndpointController urlForKey:] (E5ServiceEndpointController.m:45)
9   myApp                               0x0014968b -[E5NotificationController fetchMessages] (E5NotificationController.m:117)
10  myApp                               0x0013bec1 __41-[E5MenuPointController fetchMenuPoints:]_block_invoke (E5MenuPointController.m:172)
11  myApp                               0x002af435 __66-[RKObjectRequestOperation setCompletionBlockWithSuccess:failure:]_block_invoke244 (RKObjectRequestOperation.m:474)
12  libdispatch.dylib                   0x30aca2e3 _dispatch_call_block_and_release + 11
13  libdispatch.dylib                   0x30aca2cf _dispatch_client_callout + 23
14  libdispatch.dylib                   0x30acdd2f _dispatch_main_queue_callback_4CF + 1331
15  CoreFoundation                      0x2268f619 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
16  CoreFoundation                      0x2268dd19 __CFRunLoopRun + 1513
17  CoreFoundation                      0x225db3b1 CFRunLoopRunSpecific + 477
18  CoreFoundation                      0x225db1c3 CFRunLoopRunInMode + 107
19  GraphicsServices                    0x29c08201 GSEventRunModal + 137
20  UIKit                               0x25c4543d UIApplicationMain + 1441
21  myApp                               0x00151125 main (main.m:14)
22  libdyld.dylib                       0x30aebaaf start + 3

Thread 12:
0   libsystem_kernel.dylib              0x30ba24c4 semaphore_wait_trap + 8
1   libdispatch.dylib                   0x30ad17ff _dispatch_barrier_sync_f_slow + 363
2   CoreData                            0x22452ced _perform + 173
3   CoreData                            0x2245f991 -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 241
4   CoreData                            0x223d11df -[NSManagedObjectContext executeFetchRequest:error:] + 595
5   myApp                               0x0014baf5 -[E5ServiceEndpointController serviceEndpointUrlForKey:withHost:andContext:] (E5ServiceEndpointController.m:108)
6   myApp                               0x0014b33d -[E5ServiceEndpointController pathForKey:] (E5ServiceEndpointController.m:37)
7   myApp                               0x001ac9c7 -[E5MainViewController crashMeThreadOne] (E5MainViewController.m:357)
8   Foundation                          0x233fe68b __NSThread__main__ + 1119
9   libsystem_pthread.dylib             0x30c32e23 _pthread_body + 139
10  libsystem_pthread.dylib             0x30c32d97 _pthread_start + 119
11  libsystem_pthread.dylib             0x30c30b20 thread_start + 8

Thread 22 Crashed:
0   libobjc.A.dylib                     0x3056bf46 objc_msgSend + 6
1   CoreFoundation                      0x22681e31 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 13
2   CoreFoundation                      0x225dd6cd _CFXNotificationPost + 1785
3   Foundation                          0x23333dd9 -[NSNotificationCenter postNotificationName:object:userInfo:] + 73
4   CoreData                            0x2240dbf7 -[NSManagedObjectContext(_NSInternalAdditions) _didSaveChanges] + 2303
5   CoreData                            0x223f412f -[NSManagedObjectContext save:] + 1299
6   myApp                               0x0024774f __61-[NSManagedObjectContext(RKAdditions) saveToPersistentStore:]_block_invoke16 (NSManagedObjectContext+RKAdditions.m:65)
7   CoreData                            0x2245780d developerSubmittedBlockToNSManagedObjectContextPerform + 181
8   libdispatch.dylib                   0x30aca2cf _dispatch_client_callout + 23
9   libdispatch.dylib                   0x30ad186b _dispatch_barrier_sync_f_slow + 471
10  CoreData                            0x224579a7 -[NSManagedObjectContext performBlockAndWait:] + 183
11  myApp                               0x0024720f -[NSManagedObjectContext(RKAdditions) saveToPersistentStore:] (NSManagedObjectContext+RKAdditions.m:64)
12  myApp                               0x0013d9a9 __51-[E5MenuPointController updateNotificationCounters]_block_invoke (E5MenuPointController.m:299)
13  Foundation                          0x233e8db1 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 9
14  Foundation                          0x23353e4d -[NSBlockOperation main] + 149
15  Foundation                          0x233467c7 -[__NSOperationInternal _start:] + 775
16  Foundation                          0x233eb71b __NSOQSchedule_f + 187
17  libdispatch.dylib                   0x30ad2729 _dispatch_queue_drain + 1469
18  libdispatch.dylib                   0x30accaad _dispatch_queue_invoke + 85
19  libdispatch.dylib                   0x30ad3f9f _dispatch_root_queue_drain + 395
20  libdispatch.dylib                   0x30ad53c3 _dispatch_worker_thread3 + 95
21  libsystem_pthread.dylib             0x30c30dc1 _pthread_wqthread + 669
22  libsystem_pthread.dylib             0x30c30b14 start_wqthread + 8

All crash reports have in common that __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ is involved and that some other threads try to work with their own ManagedObjectContext (MOC) like executing a fetch or changing data on a managed object at the same time.

Our app uses RestKit 0.24 for the CoreData management and child MOC creation. We use the RestKit method -(NSManagedObjectContext*)newChildManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType tracksChanges:(BOOL)tracksChanges to create a new child MOC for each thread.

This method is quite simple and can be viewed here on gitHub

In that gitHub code you can even see that tracksChanges adds an observer with the following code [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleManagedObjectContextDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:observedContext]; and also removes the observer in the dealloc

Our finding is, that the crash does not happen if we set tracksChanges to NO. We can reproduce the crash if tracksChanges is set to YES. Keep in mind that the crash does not happen every time. It's very rare and we changed our code to endlessly rerun the problematic code pieces to have a chance to reproduce the crash.

Here is a code piece of the E5ServiceEndpointController class that can generate the crash if tracksChanges is set to YES:

- (NSString *)urlForKey:(NSString *)key
{
    NSManagedObjectContext *serviceEndpointContext = [self newChildManagedObjectContextForServiceEndpoints];
    return [self serviceEndpointUrlForKey:key withHost:YES andContext:serviceEndpointContext];
}

- (NSString *)pathForKey:(NSString *)key
{
    NSManagedObjectContext *serviceEndpointContext = [self newChildManagedObjectContextForServiceEndpoints];
    return [self serviceEndpointUrlForKey:key withHost:NO andContext:serviceEndpointContext];
}

-(NSManagedObjectContext *)newChildManagedObjectContextForServiceEndpoints{
    return [[[E5RestKitManager sharedInstance] managedObjectStore] newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType tracksChanges:YES];
}

- (NSString *)serviceEndpointUrlForKey:(NSString *)key withHost:(BOOL)includeHost andContext:(NSManagedObjectContext *)context
{
    NSFetchRequest *serviceEndpointFetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"ServiceEndpoint"];
    [serviceEndpointFetchRequest setPredicate:[NSPredicate predicateWithFormat:@"key = %@", key]];

    NSArray *result = [context executeFetchRequest:serviceEndpointFetchRequest error:nil];

    // more code here

}

What do we miss here? Do we need to protect executeFetchRequest with something? Do we need to manually update our child MOC or discard all operations if we detect a NSManagedObjectContextDidSaveNotification? Do we have an architectural misunderstanding?

Upvotes: 1

Views: 562

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70946

Looks like a simple case of not following queue confinement rules:

  1. urlForKey and pathForKey call newChildManagedObjectContextForServiceEndpoints to get a new managed object context
  2. newChildManagedObjectContextForServiceEndpoints creates this context using NSPrivateQueueConcurrencyType.
  3. urlForKey and pathForKey pass their contexts to serviceEndpointUrlForKey:andContext:
  4. serviceEndpointUrlForKey:andContext: does this:

    NSArray *result = [context executeFetchRequest:serviceEndpointFetchRequest error:nil];
    

The rule is: if you create a managed object context using queue confinement (NSPrivateQueueConcurrencyType here), you must use performBlock: or performBlockAndWait: when using that context. If you don't, you're bypassing the concurrency support that queue confinement is supposed to provide. You need to fix that, everywhere that you use one of these contexts. You should also look into using com.apple.CoreData.ConcurrencyDebug to find concurrency-related bugs.

Upvotes: 3

Related Questions