1

I am developing an iOS app that uses a SQLLite core data db. The app runs a sync loop in a background thread that grabs data from a web service and writes it to the db. The foreground (UI) thread continues while this happens, allowing the user to run searches against the db.

I am stress-testing the app and it is crashing. I am running searches against the db in the foreground while the background sync task runs. There are approx 10,000 records in the db, so it is not huge.

The background thread is created using NSOperation and it creates an NSManagedObjectContext in the main method of the NSOperation.

The foreground thread uses a different NSManagedObjectContext object initialised within (and made available by) the appDelegate.

The background sync thread writes a thousand records at a time to the db, then its managedobjectcontext performs a save.

The NSOperation main method looks like this:

-(void) main {

    NSDictionary* dictionary = [ HPSJSON getDictionaryFromData:_data ];

    // NEED to create the MOC here and pass to the methods.
    NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] init];
    [moc setUndoManager:nil];
    //[moc setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    //[moc setMergePolicy:NSOverwriteMergePolicy];
    [moc setPersistentStoreCoordinator:getApp().persistentStoreCoordinator];

    if (dictionary==nil){

        [ getApp().operationManager performSelectorOnMainThread: [ self getFailedFunction ] withObject:nil waitUntilDone:NO ];    
        return;
    }

    NSString* rc = [self processData: dictionary andMOC:moc ]; // Writes lots of records to the db and saves the moc

    // performSelectorOnMainThread invokes a method of the receiver on the main thread using the default mode.
    // i.e. call the method within HPSOperationManager as specified by the getSuccessFunction of the specialised sub-class
    [ getApp().operationManager performSelectorOnMainThread: [ self getSuccessFunction ] withObject:rc waitUntilDone:NO ];        

}

The processData method (called by main) contains lots of code, but here is the snippet that does the save:

@try {
            NSLog(@"HPSDbOperation%@ about to save Indexes moc",objectName);

            if (rcHappy==YES)
            {
                // register for the moc save notification - this is so that other MOCs can be told to merge the changes
                [[NSNotificationCenter defaultCenter] 
                 addObserver:getApp() 
                 selector:@selector(handleDidSaveNotification:)
                 name:NSManagedObjectContextDidSaveNotification 
                 object:moc];

                NSError* error = nil;
                if ([moc save:&error] == YES)
                {
                    NSLog(@"HPSDbOperation%@ Indexes SAVED",objectName);

                }else {
                    NSLog(@"HPSDbOperation%@ Indexes NOT saved ",objectName);
                }

                // unregister from notification
                [[NSNotificationCenter defaultCenter] 
                 removeObserver:getApp() 
                 name:NSManagedObjectContextDidSaveNotification 
                 object:moc];

            }



        }
        @catch (NSException * e) {
            NSLog(@"HPSDbOperationBase save Indexes Exception: %@", e);
            rcHappy=NO;
        }

The appDelegate contains the following method to handle the ManagedObjectContext merge:

- (void)handleDidSaveNotification:(NSNotification*) note 
{

    @try {

        NSLog(@"appDelegate handleDidSaveNotification about to run");
        [__managedObjectContext mergeChangesFromContextDidSaveNotification:note];
        NSLog(@"appDelegate handleDidSaveNotification did run");

    }
    @catch (NSException * e) {
        NSLog(@"appDelegate handleDidSaveNotification Exception: %@", e);
    }

}

I let the sync run, and then I stress the foreground UI thread by running successive searches. After a minute or three of syncing and searching, the app crashes. It doesn't seem to get caught by any of my try-catch constructs. The crash log in Xcode shows the following:

libobjc.A.dylib`objc_msgSend:
0x34d92f68:  teq.w  r0, #0
0x34d92f6c:  beq    0x34d92faa               ; objc_msgSend + 66
0x34d92f6e:  push.w {r3, r4}
0x34d92f72:  ldr    r4, [r0]  <-- exception here Thread1 EXC_BAD_ACCESS (Code=1)
0x34d92f74:  lsr.w  r9, r1, #2
0x34d92f78:  ldr    r3, [r4, #8]
0x34d92f7a:  add.w  r3, r3, #8
0x34d92f7e:  ldr    r12, [r3, #-8]
0x34d92f82:  and.w  r9, r9, r12
0x34d92f86:  ldr.w  r4, [r3, r9, lsl #2]
0x34d92f8a:  teq.w  r4, #0
0x34d92f8e:  add.w  r9, r9, #1
0x34d92f92:  beq    0x34d92fa6               ; objc_msgSend + 62
0x34d92f94:  ldr.w  r12, [r4]
0x34d92f98:  teq.w  r1, r12
0x34d92f9c:  bne    0x34d9317e               ; objc_msgSendSuper_stret + 34
0x34d92f9e:  ldr.w  r12, [r4, #8]
0x34d92fa2:  pop    {r3, r4}
0x34d92fa4:  bx     r12
0x34d92fa6:  pop    {r3, r4}
0x34d92fa8:  b      0x34d92fb0               ; objc_msgSend_uncached
0x34d92faa:  mov.w  r1, #0
0x34d92fae:  bx     lr

I am very new to iOS and objective-c and core-data. I'm a bit stuck on how to progress this issue. How can I tell exactly where/why the app is going wrong? Anyone have any idea why it might be crashing? I have done a bit of reading on multi-threading in core data and I believe that by creating the MOCs in the main method of NSOperation I am following guidelines, and by using handleDidSaveNotification I am also merging my MOCs correctly.

Help! Thank you.

4
  • This looks more like a memory related problem to me, can you run the test in Instruments with the zombie profiler and check if it finds anything? Commented Jul 26, 2012 at 19:03
  • If I run the search tests on their own it is fine. If I run the sync on its own it is fine. It is when I run them together that it crashes. I take it that doesn't rule out a memory problem? I'll try running it in Instruments as you suggest - thanks. Commented Jul 26, 2012 at 19:06
  • well, your crash happens in obj_msgSend which is the method that sends messages to an object (ie invokes methods on it), and it crashes with an EXC_BAD_ACCESS which indicates a faulty memory access, so I wouldn't rule out a memory related problem, but it of course can also be related to multithreading. Commented Jul 26, 2012 at 19:11
  • This probably won't help but on the off chance...try setting a breakpoint on Objective-C exceptions using the '+' sign at the bottom of Xcode's breakpoint navigator. It sometimes causes the debugger to give more useful information. Commented Jul 26, 2012 at 19:16

2 Answers 2

2

The main rule for multi-threaded Core Data use is "thread confinement" of managed object contexts and persistent stores. This means, it is safe to have thread-local managed object contexts but not passing them from one thread to another. If you do, you have to handle on your own locking and synchronization.

It seems that you create a managed object context in the secondary thread, then pass it to the main thread. Am I understanding it correctly? If so, this could explain you crash.

One possibility I am thinking of is this:

  1. background thread saves; notification is sent;

  2. main thread starts merging;

  3. while the merge is in progress, the background thread does another save.

Instead of "save" at point 3, it could be any operation you do in your processData method that puts the moc in a different state.

It seems to me that you should be able to easily verify if this occurrence is the cause of the crash by means of the log traces you already have in place.

Sign up to request clarification or add additional context in comments.

4 Comments

Yes - that is exactly what I am doing. But I need the UI thread to 'see' the new records added by the background thread, so how do I merge the background changes into the foreground context? Thanks.
However, I thought that merging of contexts is exactly what NSManagedObjectContextDidSaveNotification is all about?
Indeed, you are right... you could try anyway and put -[NSManagedObjectContext lock] before calling mergeChanges... and unlock after.
I tried wrapping my mergeChangesFromContextDidSaveNotification in a try lock/unlock but the app still crashes after a minute or two of testing (during which everything works just fine).
0

You aren't creating the context with a concurrency type, or merging changes through a parent child context relationship, so your likely problem is a threading issue. Contexts are not thread-safe, and must be accessed or mutated on the same thread that created them

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.