Joshua
Joshua

Reputation: 1994

Why Core Data concurrency debug tool crashes my iOS app?

I doubt anyone encountered such a bizarre issue recently.

When using "-com.apple.CoreData.ConcurrencyDebug 1", in the run arguments, Xcode would crash the iOS app consistently if its name (PRODUCT_NAME build setting) starts with "Music", the iOS is 14.0 or 14.01 (the latest for now), and some basic, perfectly valid, concurrency Core Data API is performed. Simply changing the PRODUCT_NAME to something else (e.g. from "Music Collection" to "My Music Collection)" would prevent the crash. So, by simply toggling the app name between the two options, and then running the debugger, one should clearly notice the app crashing and not crashing accordingly. This should be a clear evidence that the app name affects functionality.

I detected this issue since my app is available on the App Store, starts with "Music", and crashes in specific conditions that can be reproduced. When changing my app name, the same reproducible scenarios won't lead to a crash. It took me almost a week to track this down as I didn't consider that the app name can be a root cause.

This issue, where "-com.apple.CoreData.ConcurrencyDebug 1" crashes the app while valid code is executed (but the app name starts with "Music") can be easily reproduced by creating a new project in Xcode 12 and adding several lines of code. The instructions are followed in the links below.

I submitted a report to Apple Feedback Assistant: https://feedbackassistant.apple.com/feedback/8761671

I also described the issue in details on Apple Developer Forums: https://developer.apple.com/forums/thread/662602

It would be great to get any feedback.

Upvotes: 4

Views: 671

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70976

I get the same result. This looks like an internal issue that probably affects at least some other app names that begin with the same letters as Apple's iOS apps.

Here's a workaround that may help. Look at the full stack trace when the assertion happens:

* thread #2, queue = 'com.apple.root.user-initiated-qos', stop reason = EXC_BREAKPOINT (code=1, subcode=0x184e68d68)
  * frame #0: 0x0000000184e68d68 CoreData`+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__] + 12
    frame #1: 0x0000000184e68c4c CoreData`_PFAssertSafeMultiThreadedAccess_impl + 548
    frame #2: 0x0000000184e68a24 CoreData`__68-[NSManagedObjectContext _PFAutoreleasePoolReferenceQueueTrampoline]_block_invoke + 92
    frame #3: 0x0000000184e32b2c CoreData`-[_PFAutoreleasePoolThunk dealloc] + 40
    frame #4: 0x00000001801858e8 libobjc.A.dylib`AutoreleasePoolPage::releaseUntil(objc_object**) + 204
    frame #5: 0x00000001801857b8 libobjc.A.dylib`objc_autoreleasePoolPop + 236
    frame #6: 0x00000001024f8fac libdispatch.dylib`_dispatch_last_resort_autorelease_pool_pop + 40
    frame #7: 0x000000010250b37c libdispatch.dylib`_dispatch_root_queue_drain + 1428
    frame #8: 0x000000010250b928 libdispatch.dylib`_dispatch_worker_thread2 + 136
    frame #9: 0x00000001bad2175c libsystem_pthread.dylib`_pthread_wqthread + 212

Notice that frames near the middle of the pile mention autoreleasing. That suggests a course of action, since if you can affect how things are autoreleased in the framework, you might change the behavior. And in fact if you add @autoreleasepool to your performBlockAndWait:

    [managedObjectContext performBlockAndWait:^{
        @autoreleasepool {
            NSError *error = nil;
            NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"TestEnt"];
            NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
            NSLog(@"result=%@", result);
        }
    }];

...the violation goes away. I don't know if this will help in your real code as well as in your demo code, but it may be worth a try.

By the way, finding this was really good detective work. Good job.

Upvotes: 3

Related Questions