4

I have a multi-threaded app that uses Core Data. I've been seeing a lot of crashes on startup, and various bizarre error messages. However, sometimes it works fine! I have never seen a crash on my own iPhone4, but it does crash on other devices. I think I've figured out the issue, but am not sure how to best solve it.

When the app starts, I use GCD to download data from the web and update core data on a background thread. On the main thread, I continue with setting up table views, and one of these commands triggers the NSManagedObjectContext getter. I'm using pretty much the standard template code, so as it is the first time this is run, the usual lazy instanciation code creates the NSPersistentStoreCoordinator.

However the background thread is creating a new NSManagedObjectContext for it's own use. This includes getting the NSPersistentStoreCoordinator as follows:

    // Create a new NSManagedObjectContext for this thread
    NSManagedObjectContext *threadContext = nil;
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        threadContext = [[NSManagedObjectContext alloc] init];
        [threadContext setPersistentStoreCoordinator:coordinator];
        NSMergePolicy *mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
        [threadContext setMergePolicy:mergePolicy];
    } else {
        NSLog(@"Error - No NSPersistentStoreCoordinator");
    }

I'm 90% sure that issue is that both threads are getting to the NSPersistentStoreCoordinator code at the same time. Both threads believe the object is nil, therefore create the object. This causes issues for the first thread to get there as the ivar suddenly points to the wrong place. At that point bad things happen!

So how best to solve this? I have temporarily fixed the issue by adding a sleep(1) to the background thread :) but I'm not sure this is really the best solution! I have tried changing the NSPersistentStoreCoordinator properties so that they are atomic, but this hasn't helped. Should I wrap each of the standard getters in @synchronise to mutex lock them? I haven't had time to try this yet, but it should stop the same code being run by both threads. Or is there a better way?

Thoughts?

Thanks Craig

======================================

For info, here are some of the errors and crashes I have seen. These are pretty random, but it may help others find this post in the future.

2012-01-18 22:19:57.586 CBF[10468:11d03] -[NSSQLModel _addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6b80390

2012-01-18 22:19:57.595 CBF[10468:11d03] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSSQLModel _addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6b80390'

2012-01-19 16:58:06.671 CBF[11738:fe03] -[__NSCFDictionary _hasPrecomputedKeyOrder]: unrecognized selector sent to instance 0x6d55040

2012-01-25 21:45:31.174 CBF[16911:1310b] -[__NSArrayM _addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6d59370

2012-01-25 21:45:31.175 CBF[16911:1310b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM _addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6d59370'

Craig Watkinson
  • 903
  • 10
  • 20

1 Answers1

4

Why not just remove the lazy instantiation, and instead create the coordinator immediately when the app starts?

paulbailey
  • 5,328
  • 22
  • 35
  • 1
    Thanks Paul. You see, it's so obvious now you say that! Actually, I could also leave the lazy instantiation but just make sure that I specifically call the NSManagedObjectContext getter to kick the whole setup process off and throw the result away. If I do this before any other core data stuff, then all will be ok. Many thanks. – Craig Watkinson Jan 26 '12 at 13:04
  • Hi Craig, I did not understand the solution can you explain in more detail, coz facing similar issue when synching core data and user try to view particular record which getting deleted – Kiran S Feb 03 '12 at 08:46
  • 1
    @iphonegeek It was caused by 2 threads accessing the core data stack at same time. This is normally OK, however the first time it is used, there is the usual lazy instantiation code that says `if blah=nil then do alloc/init` The issue was that the second thread got to the method and the ivars were still nil as the alloc/init being done by the first thread hadn't completed. The solution was to add the following line in the main thread before forking the 2nd thread: `NSManagedObjectContext *unused __attribute__((unused)) = [self managedObjectContext];` This ensures everything is created. – Craig Watkinson Feb 17 '12 at 15:24