views:

800

answers:

1

Update

I wrote a minimal code example of the issue I am having. I implemented the background work two ways: manually spawning threads and letting NSOperation handle threading. In both cases I am creating NSManagedObjectContexts for each thread/operation.

When I spawn the threads myself with performSelectorInBackground:withObject: everything works fine. When I switch to passing my objects off to an NSOperationQueue I see the following errors when attempting to save the operations NSManagedObjectContext.

  • EXC_BAD_ACCESS
  • Serious application error. Exception was caught during Core Data change processing: * -[NSCFSet addObject:]: attempt to insert nil with userInfo (null)
  • _referenceData64 only defined for abstract class. Define -[NSTemporaryObjectID_default _referenceData64]!

I believe the bug, especially given the last error, has to do with using a temporary objectID to pass objects between threads/contexts. Possibly, even worse, I'm somehow passing NSManagedObjects between threads.

Either way, I can't find any code which would suggest I am doing so.

My minimal code example can be found at http://matthewcrandall.net/MinimalCodeExample.zip

Most of the work is done in the AppDelegate in awakeFromNib. Set EXECUTE_WITH_NSOPERATION to 0 to run with performSelectorInBackground:withObject:. Leave EXECUTE_WITH_NSOPERATION on 1 to execute with the NSOperationQueue which creates a bunch of MCBoardParse objects.

I'm only seeing this under 10.6.

Original

I have a Cocoa application built on 10.5 frameworks. In an NSOperation In a loop I am quickly creating hundreds of NSManagedObjects. Frequently the creation of those NSManagedObejcts will crash with a exc_bad_access error. This happens under both reference counted memory management and garbage collection.

    for (offsetCount; offsetCount < [parsedData count]; offsetCount++) {
  NSManagedObject *child = [NSEntityDescription insertNewObjectForEntityForName:@"Thread" inManagedObjectContext:[self moc]];
  Thumbnail *thumb = [Thumbnail insertInManagedObjectContext:[self moc]];
  Image *image = [Image insertInManagedObjectContext:[self moc]];
  ...
 }

Thumbnail and Image are both subclasses of NSManagedObject generated with mogenerator. insertInManagedObjectContext: looks like

    NSParameterAssert(moc_);
    return [NSEntityDescription insertNewObjectForEntityForName:@"Thumbnail" inManagedObjectContext:moc_];

    NSParameterAssert(moc_);
 return [NSEntityDescription insertNewObjectForEntityForName:@"Image" inManagedObjectContext:moc_];
The NSManagedObjectContext returned by [self moc] is created for the NSOperation with 

    NSPersistentStoreCoordinator *coord = [(MyApp_AppDelegate *)[[NSApplication sharedApplication] delegate] persistentStoreCoordinator];
 self.moc = [[NSManagedObjectContext alloc] init];
 [self.moc setPersistentStoreCoordinator:coord];
 [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(contextDidSave:) 
       name:NSManagedObjectContextDidSaveNotification 
     object:self.moc];
 [self.moc setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
 [self.moc setUndoManager:nil];
 [self.moc setRetainsRegisteredObjects:YES];

moc is defined as (nonatomic, retain) and synthesized. As far as I can tell it, the persistent store and my appDelegate have no reason to be and are not being garbage collected.

The stack trace looks like

Thread 2 Crashed:  Dispatch queue: com.apple.root.default-priority
0   libauto.dylib                  0x00007fff82d63600 auto_zone_root_write_barrier + 688
1   libobjc.A.dylib                0x00007fff826f963b objc_assign_strongCast_gc + 59
2   com.apple.CoreFoundation       0x00007fff88677068 __CFBasicHashAddValue + 504
3   com.apple.CoreFoundation       0x00007fff88676d2f CFBasicHashAddValue + 191
4   com.apple.CoreData             0x00007fff82bdee5e -[NSManagedObjectContext(_NSInternalAdditions) _insertObjectWithGlobalID:globalID:] + 190
5   com.apple.CoreData             0x00007fff82bded24 -[NSManagedObjectContext insertObject:] + 148
6   com.apple.CoreData             0x00007fff82bbd75c -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:] + 716
7   com.apple.CoreData             0x00007fff82bdf075 +[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] + 101
8   com.yourcompany.MyApp         0x000000010002c7a7 +[_Thumbnail insertInManagedObjectContext:] + 256 (_Thumbnail.m:14)
9   com.yourcompany.MyApp         0x000000010002672d -[ThreadParse main] + 10345 (ThreadParse.m:174)
10  com.apple.Foundation           0x00007fff85ee807e -[__NSOperationInternal start] + 698
11  com.apple.Foundation           0x00007fff85ee7d23 ____startOperations_block_invoke_2 + 99
12  libSystem.B.dylib              0x00007fff812bece8 _dispatch_call_block_and_release + 15
13  libSystem.B.dylib              0x00007fff8129d279 _dispatch_worker_thread2 + 231
14  libSystem.B.dylib              0x00007fff8129cbb8 _pthread_wqthread + 353
15  libSystem.B.dylib              0x00007fff8129ca55 start_wqthread + 13

My app is crashing in other places with exc_bad_access but this is code that it happens most with. All of the stack traces look similar and have something to do with CFHash.

Any help would be appreciated.

A: 

If you are crashing with a exc_bad_access that means you are over-releasing an object or you are calling methods on an object after it has been released. Both of these situations are bad and have nothing to do with Core Data. The fact that you are using garbage collection is probably a clue that something is getting dereferenced and therefore getting garbage collected before you expect it to.

First question though, are you creating a new NSManagedObjectContext for each one of these NSOperation instances?

Second, I would recommend turning on NSZombie (which I believe you can do through instruments now) and that will help narrow down what code is calling an object after release, etc. Doing a Google search on NSZombie and Instruments will turn up several how-to articles.

update

Since this is a 10.6 issue only then it may have to do with the NSOperation instances instead of the Core Data instances. Are your operations flagged as concurrent? The reason I ask is that NSOperation ignores the concurrent flag on 10.6 and that can lead to some nasty surprises.

update 2

Just a note while I am reviewing the overall problem. The line:

self.moc = [[NSManagedObjectContext alloc] init];

Will leak memory (at least while GC is off) if you do not have a release as the alloc init will increment the retain count and then the [self setMoc:] call also increments the retain count.

Marcus S. Zarra
Marcus, thank you for taking the time to respond to my question. Your book on Core Data is excellent.I am creating a an NSManagedObjectContext for each NSOperation instance. Most of my NSOperation instances only exist for less than a second and update one or two NSManagedObjects. The NSOperations that are causing the exc_bad_access are, by design, last much longer and touching thousands of NSManagedObjects. While multiple of these longer running NSOperations run at the same time they never work on the same objects.
matthewc
No NSZombie instances are being triggered when I have an exc_bad_access crash. I also tried setting NSZombieEnabled as an executable variable in XCode to double check Instruments results. At runtime I tested and saw that NSZombieEnabled was set but the exc_bad_access still did not hit an NSZombie instance.I am also attempting to track the collections of my objects with log statements in finalize methods. I haven't subclassed NSManagedObjectContext to try this yet but the rest of the objects used in the calls to create the objects have not yet run their finalize methods.
matthewc
I should note that today while I was attempting to fix this issue I saw that it is nonexistent on 10.5 machines. It is only happening on 10.6 machines.
matthewc
Thank you for your updated response.My operations are not flagged as concurrent.
matthewc
Thank you for the leak catch.
matthewc