Reputation: 1154
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 ManagedObjectContext
s 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
Reputation: 70946
Looks like a simple case of not following queue confinement rules:
urlForKey
and pathForKey
call newChildManagedObjectContextForServiceEndpoints
to get a new managed object contextnewChildManagedObjectContextForServiceEndpoints
creates this context using NSPrivateQueueConcurrencyType
.urlForKey
and pathForKey
pass their contexts to serviceEndpointUrlForKey:andContext:
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