Łukasz Sromek
Łukasz Sromek

Reputation: 3687

Deadlock when saving context with concurrency type NSPrivateQueueConcurrencyType

Since two days I'm trying to get Core Data to work with multiple threads. I tried standard thread confinement method with NSOperations, merging notifications, using objectWithId, dictionaries of contexts per thread and still I get strange deadlocks, inconsistency exceptions and a bunch of other nasty stuff. It's driving me crazy... moreover I can't find a single example or explanation on how to manage context in two threads when both threads may make changes to the shared persistent store...

I tried to use new iOS 5 method, that supposed to be easier, but still I get errors. The first problem is the deadlock when saving context. I removed all the unnecessary code and stil get deadlocks when executing this code fast enough (by quickly tapping a button):

    NSManagedObjectContext *context = [StoreDataRetriever sharedRetriever].managedObjectContext;

    for (int i = 0; i < 5; i++) {            
        NSError *error = nil;
        NSLog(@"Main thread: %@, is main? %d", [NSThread currentThread], [NSThread isMainThread]);
        BOOL saveOK = [context save:&error];

        if (!saveOK) {
            NSLog(@"ERROR!!! SAVING CONTEXT IN MAIN");
        }

        [context performBlock:^{

            NSLog(@"Block thread: %@", [NSThread currentThread]);

            NSError *error = nil;
            BOOL savedOK = NO;

            savedOK = [context save:&error]; 

            if (!savedOK) {
                NSLog(@"ERROR!!! SAVING CONTEXT IN BLOCK");
            }
        }];
    }

There are no other changes to the database, nothing, only saving context. What is wrong with this code? How should it look like?

Note: [StoreDataRetriever sharedRetriever].managedObjectContext is created in appDelegate using initWithConcurrencyType:NSPrivateQueueConcurrencyType.

Upvotes: 1

Views: 1283

Answers (1)

What's going on with that code? You are saving the context on a thread synchronously, then you schedule a save on the context private queue. 5 times. So basically, you may well have two save operations, one synchronous and one asynchronous, colliding with each other.

This is clearly an issue. You aren't supposed to save a context with a private queue outside of that queue. It will work with the current context implementation provided there is no scheduled block on the context queue. But this is wrong nevertheless.

…
for (int i = 0; i < 5; i++) {            
    NSLog(@"Main thread: %@, is main? %d", [NSThread currentThread], [NSThread isMainThread]);
    __block NSError *error = nil;
    __block BOOL saveOK = YES;
[context performBlockAndWait: ^{
    saveOK = [context save: &error];
}];

    if (!saveOK) {
        NSLog(@"ERROR!!!");
    }
    …

With that code, you execute the save operation synchronously and most certainly on the same thread - thanks GCD - sparing context switches and synchronization stuff, and without any risk of having two operations running on that context at the same time.

The same rule applies when using NSMainQueueConcurrencyType, with an exception. That queue is bound to the main thread and the main thread only. You can schedule blocks on a context using the main queue from any thread with performBlock and performBlockAndWait like NSPrivateQueueConcurrencyType, and (the exception:) you can use the context directly on the main thread.

NSConfinementConcurrencyType binds the context to a specific thread and you cannot use GCD or blocks to deal with such a context, only the bound thread. There is very little reasons to use that concurrency model as of today. If you have to, use it, but if you do not absolutely have to, don't.


edit

Here is a very nice article about multi-contextes setups: http://www.cocoanetics.com/2012/07/multi-context-coredata/

Upvotes: 2

Related Questions