罕见的崩溃与多个子ManagedObjectContexts和NSManagedObjectContextDidSaveNotification

时间:2015-04-09 13:39:12

标签: ios multithreading core-data restkit nsmanagedobjectcontext

我们从生产应用程序返回崩溃报告。它崩溃了大约1%的应用程序开放,我们无法让应用程序在XCode会话期间或模拟器上崩溃。但是我们能够在没有运行XCode会话的设备上重现崩溃。我认为这是多个ManagedObjectContext之间的竞争条件,试图通过NSManagedObjectContextDidSaveNotification

了解更改

但首先是一个示例崩溃报告:

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

所有崩溃报告的共同点都是__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__,其他一些线程尝试使用自己的ManagedObjectContext(MOC),例如同时执行获取或更改托管对象上的数据。

我们的应用程序使用RestKit 0.24进行CoreData管理和子MOC创建。我们使用RestKit方法 -(NSManagedObjectContext*)newChildManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType tracksChanges:(BOOL)tracksChanges为每个帖子创建一个新的子MOC。

此方法非常简单,可以查看here on gitHub

在该gitHub代码中,您甚至可以看到tracksChanges添加了一个包含以下代码[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleManagedObjectContextDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:observedContext];的观察者,并且还删除了dealloc

中的观察者

我们的发现是,如果我们将tracksChanges设置为NO,则不会发生崩溃。如果tracksChanges设置为YES,我们可以重现崩溃。请记住,每次都不会发生崩溃。这是非常罕见的,我们改变了我们的代码,无休止地重新运行有问题的代码片段,以便有机会重现崩溃。

以下是E5ServiceEndpointController类的代码段,如果tracksChanges设置为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

}

我们在这里想念什么?我们需要保护executeFetchRequest吗?如果我们检测到NSManagedObjectContextDidSaveNotification,我们是否需要手动更新我们的子MOC或丢弃所有操作?我们是否存在架构上的误解?

1 个答案:

答案 0 :(得分:3)

看起来像是一个不遵循队列限制规则的简单案例:

  1. urlForKeypathForKey调用newChildManagedObjectContextForServiceEndpoints以获取新的托管对象上下文
  2. newChildManagedObjectContextForServiceEndpoints使用NSPrivateQueueConcurrencyType创建此上下文。
  3. urlForKeypathForKey将其上下文传递给serviceEndpointUrlForKey:andContext:
  4. serviceEndpointUrlForKey:andContext:执行此操作:

    NSArray *result = [context executeFetchRequest:serviceEndpointFetchRequest error:nil];
    
  5. 规则是:如果使用队列限制创建托管对象上下文(此处为NSPrivateQueueConcurrencyType),则必须使用performBlock:performBlockAndWait:上下文。如果你不这样做,你就会绕过队列限制应该提供的并发支持。您需要在使用其中一个上下文的任何地方修复它。您还应该考虑使用com.apple.CoreData.ConcurrencyDebug来查找与并发相关的错误。