Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EXC_BAD_ACCESS (SIGSEGV) when using deleteObject: on Core Data store

In my application, I'm removing (or trying to remove) all records from two core data stores before adding new ones in place. They are 2 simple stores, containing data related to records in the address book (VIContacts contains contact id and a vcard hash (integer), VIGroup contains group id and group name).

To remove all contacts from a store, I use this piece of code, in a method called -clear::


NSArray *allOldRowsInVIContacts = [[mainContext fetchObjectsForEntityName:[VIContact name]
                                               includePropertyValues:NO
                                               withPredicate:nil] copy];

for (NSManagedObject *obj in allOldRowsInVIContacts) {
    @try {
        [mainContext deleteObject:obj];
    }
    @catch (NSException *exception) {
        NSLog(@"Exception Triggered: %@", exception.reason);
        [NSException raise:exception.reason format:@"thrown on vicontacts."];
    }
}

[allOldRowsInVIContacts release];

if (![mainContext save:error]) {
    return NO;
}

NSArray *allOldRowsInVIGroups = [[mainContext fetchObjectsForEntityName:[VIGroup name]
                                                 includePropertyValues:NO
                                                         withPredicate:nil] copy];

NSLog(@"all rows in VIGroups count: %d", [allOldRowsInVIGroups count]);

for (NSManagedObject *obj in allOldRowsInVIGroups) {
    @try {
        [mainContext deleteObject:obj];
    }
    @catch (NSException *exception) {
        NSLog(@"Exception Triggered: %@", exception.reason);
        [NSException raise:exception.reason format:@"thrown on vigroups."];
    }
}

[allOldRowsInVIGroups release];

NSLog(@"at the end of -clear: Going to save context.");

/* SAVE */
if (![mainContext save:error]) {
    return NO;
}

The app always seem to crash around the VIGroup region.

The crash log is as follows:


Crashed Thread:  5  Dispatch queue: com.apple.root.default-priority

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000000
...
Thread 5 Crashed:: Dispatch queue: com.apple.root.default-priority
0   com.apple.CoreFoundation        0x00007fff82532574 __CFBasicHashRehash + 1412
1   com.apple.CoreFoundation        0x00007fff8252b41b __CFBasicHashAddValue + 75
2   com.apple.CoreFoundation        0x00007fff82531f78 CFBasicHashAddValue + 3176
3   com.apple.CoreFoundation        0x00007fff82547899 CFSetAddValue + 121
4   com.apple.CoreData              0x00007fff8520e3dc -[NSManagedObjectContext deleteObject:] + 220
5   com.andrei.AddressBookApp       0x000000010004da9a -[AddressBookFrameworkSyncHelper clear:] + 490
6   com.andrei.AddressBookApp       0x000000010004c8f9 +[AddressBookFrameworkSyncHelper saveSnapshot:] + 105
7   com.andrei.AddressBookApp       0x000000010002d417 -[SLSyncOperation main] + 2631
8   com.apple.Foundation            0x00007fff8b68dbb6 -[__NSOperationInternal start] + 684

Other Info

I have used Instruments to look for zombies, but none show up. There are some leaks in the application, but none related to Core Data.

There is no relationship between VIGroup and VIContact. They are separate, stand-alone entities.

Weirdly enough, the code never seems to go into @catch, as the console doesn't pick up any Exception triggered: ... messages before it crashes.

The error is thrown every now and then. The app seems to be more stable on Lion, but it crashes frequently on Mountain Lion and Snow Leopard.

Thanks. Any help is greatly appreciated.

Updated with some more code

MOC Created:

I create an 'NSOperation' ('SLSyncOperation') and add it to a 'NSOperationQueue'. That SLSyncOperation is added to an NSOperationQueue:


[backgroundQueue setMaxConcurrentOperationCount:1];

// has a custom initializer
currentOperation = [[SLSyncOperation alloc] initWithPersistentStoreCoordinator:persistentStoreCoordinator
                                                                   andDelegate:delegate
                                                               forceRemoteSync:forceSync];

[backgroundQueue addOperation:currentOperation];

This is the main method of the SLSyncOperation (inherits from NSOperation):


- (void)main {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    syncProgressTracker = [SLSyncProgressTracker sharedProgressTracker];
    syncProgressTracker.currentStatus = SLSyncStatusIdle;

    // ... some other setup and sending notifications ...

    /* Set up. */
    managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    managedObjectContext = [[NSManagedObjectContext alloc] init];

    // persistentStoreCoordinator is passed from the app delegate
    [managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

    // ... continues with other logic (syncing to a server), and the end of the method is: ...

    /* Tear down. */
    [managedObjectContext release];
    managedObjectModel = nil;

    [pool drain];
}

MOC being used:

I'm using the MOC in a singleton class, which is called from methods called from within the SLSyncOperation. I assume that in this case everything is happening in the same thread...? I will add some test methods to check this out.

MOC being initialized in singleton class:


+ (AddressBookFrameworkSyncHelper *)sharedHelper {
    if (!_sharedAddressBookHelper) {
        _sharedAddressBookHelper = [[AddressBookFrameworkSyncHelper alloc] init];
    }

    return _sharedAddressBookHelper;
}

- (id)init {
    if (self = [super init]) {        
        mainContext = [(AddressBookAppAppDelegate *)[[NSApplication sharedApplication] delegate] managedObjectContext];
        addressBookRef = [ABAddressBook sharedAddressBook];

        // disable undo manager - uses less memory
        [mainContext setUndoManager:nil];        
    }

    return self;
}

After this, I'm using the MOC (mainContext) for saving, passing it around to other methods that use it etc. e.g.


//saving
[sharedABF.mainContext save:error];

// passing it to a Core Data method
VIContact *contactToAdd = [VIContact newOrExistingContactWithID:contactID
                                                      inContext:sharedABF.mainContext
                                                          error:error];

// that method looks like this
+ (VIContact *)newOrExistingContactWithID:(NSString *)contactID inContext:(NSManagedObjectContext *)context error:(NSError **)error {    
    VIContact *theContact = [[context fetchObjectsForEntityName:[VIContact name]
                                          includePropertyValues:YES
                                                  withPredicate:
                              @"personID == %@", contactID] lastObject];

    if (theContact) {
        return [theContact retain];
    } else {
        // no contact found with that ID, return a new one
        VIContact *newContact = [[VIContact alloc] initAndInsertInContext:context];
        newContact.personID = contactID;
        return newContact;
    }
}

// and then fetch all rows in a Core Data entity and remove them
NSArray *allOldRowsInVIContacts = [mainContext fetchObjectsForEntityName:[VIContact name]
                                                   includePropertyValues:NO
                                                           withPredicate:nil];

for (NSManagedObject *obj in allOldRowsInVIContacts) {
    [mainContext deleteObject:obj];
}

if (![mainContext save:error]) {
    return NO;
}

The fetchObjectsForEntityName method is taken from here.

I will try to see if the method is accessed from different threads with those methods you were mentioning. Hope this helps and gives you more info about how I'm using the MOC.

More Information

I've named the thread where the mainContext is created as SLSyncOperationThread.Name set.. Before the point where the app crashes, I put an NSLog which prints out the name of the thread. It prints out this thread's name every time. So it doesn't seem to be a multi-thread issue. Especially because the app crashes every now and then now every time it reaches that point.

like image 780
Alex Avatar asked Aug 31 '12 10:08

Alex


2 Answers

First, you say that the entities do not share a relationship, but what relationships do they have?

Second, you are getting a segmentation fault because a NULL pointer is being dereferenced.

Third, how many NSManagedObjectContext objects do you have, and how are they being accessed?

It looks like your clear method is being called from within an NSOperationQueue but it is not running on the main thread. You are not supposed to access a single MOC from multiple threads concurrently.

My initial bet (without further information) is that you are accessing the MOC from multiple threads, which is a very bad thing.

Also, it looks like you are using Matt Gallagher's one line fetch. I think it returns a NSSet, not NSArray... not that it seems to matter in this case, but always good to make sure you are using the proper types.

EDIT

Unfortunately, you did not show how you are calling your clear method. I bet it is in the stuff you replaced with the comment:

// ... continues with other logic (syncing to a server), and the end of the method is: ...

In any event, I am convinced that my first bet was right, and you are using the mainContext MOC from a different thread. Your SLSyncOperation is running in its own thread, and is using its own MOC (the one it creates). However, within that operation, it appears to be calling saveSnapshot which calls clear which, in turn, is using mainContext and not the MOC that was created to be used in that NSOperation.

The mainContext is the MOC owned by the AppDelegate, and used for stuff in the main thread. It is being called and used in this "other" thread as well. See your stack trace for proof. That's rule number 1 of Core Data and threads. You must not allow multiple threads concurrent access to the same MOC.

So, you are spinning a separate thread to do some work, and creating a local MOC to do the work. All well and good. However, you are still, from that thread, calling a method that explicitly wants to use the mainContext (in this case saveSnapshot is being called directly from your operation's main.

Now, you have several options:

Pass the loacally generated MOC to those methods so they operate on the proper MOC. Maybe you intend to be snapshotting that MOC instead?

If you really intend for these methods to operate on the mainContext MOC, then you need to make sure they execute in the main thread. I don't like the perform selectors, and prefer direct GCD.

dispatch_async(dispatch_get_main_queue(), ^{
    // Now you can call the saveSnapshot and other stuff that must use
    // mainContext since stuff in this block will execute on the main thread.
});

If you are using a main MOC, and have any other threads, I strongly encourage you to NOT use confinement concurrency for the main MOC. Instead, I suggest this alternative:

managedObjectContext = [[NSManagedObjectContext alloc]
                        initWithConcurrencyType:NSMainQueueCurrencyType];

Now, that MOC can be used just like the other one, with no code change (in other words, it will still act appropriately like a confinement MOC when used on the main thread). However, it can more appropriately be used from other threads as well:

[managedObjectContext performBlock:^{
    // Do anything with this MOC because its protected, and
    // running on the right thread.
}];

If necessary, you can call performBlockAndWait which is reentrant (dispatch_sync is not reentrant - it will cause a deadlock). You still have to be careful with any sync operation to avoid a deadly embrace but performBlockAndWait can be called recursively from the same thread without deadlocking.

like image 27
Jody Hagins Avatar answered Oct 06 '22 10:10

Jody Hagins


Try to simply delete the file instead of the objects:

- (void)emptyDatabase{
    NSError * error;
    // retrieve the store URL
    NSURL * storeURL = [[self.managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[self.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
    // lock the current context
    [self.managedObjectContext lock];
    [self.managedObjectContext reset];//to drop pending changes
    //delete the store from the current managedObjectContext
    if ([[self.managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[self.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error])
    {
        // remove the file containing the data
        [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
        //recreate the store like in the  appDelegate method
        [[self.managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
    }
    [self.managedObjectContext unlock];
}
like image 114
Resh32 Avatar answered Oct 06 '22 08:10

Resh32