Core data crash occurring since iOS 10

懵懂的女人 提交于 2020-01-13 04:56:30

问题


I've been struggling to resolve a locally-unreproducible crash since iOS 10 within the sqlite/coredata library. It's occurring very infrequently - somewhere in the realm of 0.2% in production.

What I know (or at least suspect):

  • It only happens on iOS 10 and above.
  • Most often occurs while saving the context, but may also be occurring during a core data fetch request.
  • Occurring fairly rarely (ballpark rate of 0.15% of sessions)
  • I have run stress tests with concurrency debug flags enabled, as well as some of the xcode memory management tools. No issues detected.
  • Tested for memory leaks.
  • I've never been able to reproduce this stacktrace in a development environment.
  • No exceptions are being thrown prior to the crash. The entire code is wrapped.
  • This action is performed within a block, and the app is in the foreground.
  • Occurs seemingly at random during normal app operation. (Not at initialization time or anything special)
  • It's a SIGABRT crash

libsystem_kernel.dylib0x00000001841c3014 __pthread_kill+4 libsystem_c.dylib0x0000000184137400 abort+136 libsystem_malloc.dylib0x0000000184207a5c nanozone_error+328 libsystem_malloc.dylib0x0000000184209028 nano_realloc+644 libsystem_malloc.dylib0x00000001841fb240 malloc_zone_realloc+176 libsqlite3.dylib0x0000000185730c34 sqlite3_value_text+1220 libsqlite3.dylib0x0000000185777f38 sqlite3_rekey+1564 libsqlite3.dylib0x000000018578df78 sqlite3_rekey+91740 libsqlite3.dylib0x0000000185791c88 sqlite3_rekey+107372 libsqlite3.dylib0x000000018571df98 sqlite3_log+86448 libsqlite3.dylib0x0000000185757780 sqlite3_bind_int+11992 libsqlite3.dylib0x00000001856f1c80 sqlite3_exec+35188 libsqlite3.dylib0x00000001856eb608 sqlite3_exec+8956 libsqlite3.dylib0x00000001856ea838 sqlite3_exec+5420 libsqlite3.dylib0x00000001856e9f24 sqlite3_exec+3096 libsqlite3.dylib0x00000001856e9ae0 sqlite3_exec+2004 CoreData0x00000001874f1284 -[NSSQLiteConnectionprepareSQLStatement:]+468 CoreData0x00000001876166f0 -[NSSQLiteConnectionupdateRow:forRequestContext:]+496 CoreData0x00000001876c3430 _writeChangesForSaveRequest+1596 CoreData0x00000001876c4958 _executeSaveChangesRequest+312 CoreData0x00000001876ba7f4 -[NSSQLSaveChangesRequestContextexecuteRequestUsingConnection:]+40 CoreData0x00000001875cdaf8 __52-[NSSQLDefaultConnectionManagerhandleStoreRequest:]_block_invoke+256 libdispatch.dylib0x000000018407e1bc _dispatch_client_callout+12 libdispatch.dylib0x000000018408b7f0 _dispatch_barrier_sync_f_invoke+80 CoreData0x00000001875cd994 -[NSSQLDefaultConnectionManagerhandleStoreRequest:]+204 CoreData0x0000000187693f80 -[NSSQLCoreDispatchManagerrouteStoreRequest:]+284 CoreData0x00000001875fb7e4 -[NSSQLCoredispatchRequest:withRetries:]+196 CoreData0x00000001875f7560 -[NSSQLCoreprocessSaveChanges:forContext:]+200 CoreData0x00000001874f8360 -[NSSQLCoreexecuteRequest:withContext:error:]+744 CoreData0x00000001875da2f4 __65-[NSPersistentStoreCoordinatorexecuteRequest:withContext:error:]_block_invoke+3248 CoreData0x00000001875d2bf0 -[NSPersistentStoreCoordinator_routeHeavyweightBlock:]+272 CoreData0x00000001874f7f20 -[NSPersistentStoreCoordinatorexecuteRequest:withContext:error:]+404 CoreData0x00000001875195ac -[NSManagedObjectContextsave:]+2768

Here's what the code generally looks like:

NSManagedObject *object = [[MyManagedObject alloc] init];

// This is actually within the init method
NSEntityDescription *desc = [NSEntityDescription entityForName:NSStringFromClass(object.class)
                                      inManagedObjectContext:context];

[object initWithEntity:desc insertIntoManagedObjectContext:nil];


// later on...
[context performBlock:^{

    // Fetch another (different) object from core data
    NSArray *fetchResults = [context executeFetchRequest:request error:&error];

    // Changing some properties of object with values from fetched results
    object.property = fetchResults[0].property;

    // insert the object
    [context insertObject:object];

    // save the context
    [context save:&error]
}

Any ideas would be greatly appreciated.

Update:

I found this release note that coincided with iOS 10.2, which may have caused some existing issue(s) to be exposed. It's not clear what the change was, or how it might cause problems, but it seems pretty likely that this is related somehow.

https://support.apple.com/en-us/HT207422 Impact: Processing malicious strings may lead to an unexpected application termination or arbitrary code execution Description: A memory corruption issue existed in the processing of strings. This issue was addressed through improved bounds checking. CVE-2016-7663


回答1:


If the majority of your code base is, as your suggest, asynchronous and you are trying to perform a synchronous save operation within this asynchronous block, there is every reason to suspect that is why you're receiving the NSPersistentStoreCoordinator error in the error message.

The key is the issue with the NSPersistentStoreCoordinator (PSC) failing to properly coordinate the data save. Unless I'm mistaken the error message identifies that the PSC is locked when you ask the PSC to respond to the call to save for that MOC.

In my humble opinion your problem still most likely stems from your call to performBlock... in this code you're performing a fetch request, then updating a property, then inserting the object back into the MOC, then saving, all in the same block. These are very different functions taking different amounts of processing power and time, all dumped into one single concurrency block.

Also, how you instantiate a property when using concurrency and blocks is important. You may need to check where in your code is most appropriate to instantiate your properties.

So some questions...

  1. Do you need every line of this code in a performBlock? Consider that, unless you're blocking your UI, the fetch request and the update to your property may be ok in code outside the call to performBlock.
  2. If you do need every line of this code in a core data concurrency block such as performBlock, have you considered instead embedding your call to save in a "block-within-a-block" and using performBlockAndWait?

The Apple developer website has an example of embedding a save call into a performBlockAndWait block, included (in part) following:

NSManagedObjectContext *moc = '…; //Our primary context on the main queue

NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:moc];

[private performBlock:^{
    'Complete your fetch request and update the managed object's property.

    NSError *error = nil;
    if (![private save:&error]) {
        NSLog(@"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]);
        abort();
    }
    [moc performBlockAndWait:^{
        NSError *error = nil;
        if (![moc save:&error]) {
            NSLog(@"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]);
            abort();
        }
     }];
}];

If you're able to update your question with a little more code and a more detailed description I might be able to provide a more accurate fix for your specific problem.

Also I'd recommend you do some research...

Despite the age of the book, the concepts of concurrency are still very well explained in "Core Data, 2nd Edition, Data Storage and Management for iOS, OS X, and iCloud" (Jan 2013 from The Pragmatic Bookshelf) by Marcus S. Zarra, and in particular Chapter 4 titled "Performance Tuning” and Chapter 5 titled "Threading”.

Another valuable book on core data from Apress publishers – "Pro iOS Persistence Using Core Data", by Michael Privat and Robert Warner.




回答2:


No longer occurs as of iOS 10.3. Root cause is still unknown. Assuming some iOS memory management break in 10.2, with a fix in 10.3.



来源:https://stackoverflow.com/questions/42519574/core-data-crash-occurring-since-ios-10

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!