Jerry
Jerry

Reputation: 1028

Race Condition of coreData executeFetchRequest method cause the issue of nil data

I run into a race condition that I have no clue how to solve it. getPurchasedBookTitles and getBookEntryIDs are in different threads using NSOperationQueue, they're competing with each other calling fetchBooks (which is a method in a different class). I put managedObjecContext (parent, child, root) with lock/unlock to avoid the problem of racing, but it doesn't seems to solve the root problem.

The issue is that book.bookTitle in the for-loop (Book *book in allBooks) will be turned to nil sometime and leads to app crashing or hanging.

Thanks in advance!

// in a singleton class, dispatch_once

- (NSArray *)getPurchasedBookTitles
{
    NSMutableArray *bookTitles = [[NSMutableArray alloc] init];

    NSArray *allBooks = [[CoreDataManager sharedInstance] fetchBooks];

    for (Book *book in allBooks)
    {
        if (book.bookTitle != nil && book.is_owned != nil )
            if (![bookTitles containsObject:book.bookTitle] && [[book is_owned] boolValue] == YES)
                [bookTitles addObject:book.bookTitle];
    }

    return bookTitles;
}

- (NSArray *)getBookEntryIDs
{    
    NSMutableArray *bookTitles = [[NSMutableArray alloc] init];

    NSArray *allBooks = [[CoreDataManager sharedInstance] fetchBooks];

    for (Book *book in allBooks)
    {
        if (book.bookTitle != nil)
            if (![bookTitles containsObject:book.bookTitle])
                [bookTitles addObject:book.bookTitle];
    }

    return bookTitles;
}

// in the class coreDataManager // managedObjectContextChild, managedObjectContext, writerManagedObjectContext are declared and instantiated in the appDelegate and coreDataManager classes in details.

- (NSArray *)fetchBooks
{        
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"bookTitle" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:bookEntity];
    [request setPredicate:nil];
    [request setSortDescriptors:sortDescriptors];

    [managedObjectContextChild lock];
    [managedObjectContext lock];
    [writerManagedObjectContext lock];

    NSError *error = NULL;

    NSArray *results = [managedObjectContextChild executeFetchRequest:request error:&error];

    if (error != NULL)
        NSLog(@"Error fetching - %@", error);

    [writerManagedObjectContext unlock];
    [managedObjectContext unlock];
    [managedObjectContextChild unlock];

    return results;
}

Upvotes: 0

Views: 643

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70936

Core Data is not thread-safe, and you need to take steps to deal with that. I'm not sure what's setting bookTitle to nil, but there are a couple of major issues with the code above:

  1. "Not thread safe" means you can't use Core Data objects at all on multiple threads unless you can guarantee that all access is synchronized. Synchronizing fetching via locks only covers part of it-- you can't use the fetched objects on multiple threads simultaneously either. You'll be using the same Book instances on multiple threads, which pretty much guarantees problems. The usual approach is to give different threads/queues their own managed object contexts, and to synchronize changes as they are made.

  2. Locking can help but only if you're certain you've hit every possibility. It's hard to get right and prone to deadlocking. This is why Core Data includes an API specifically designed to deal with the situation. Look into the performBlock and performBlockAndWait methods for help here. Use those instead of your own locking scheme.

Upvotes: 2

Related Questions