Core Data nested managed object contexts and frequent deadlocks

  • I have a parent MOC setup with NSPrivateQueueConcurrencyType and a persistent store coordinator set, it has a child MOC setup with NSMainQueueConcurrencyType. The idea being most of the long hard work and saves can be done on the private MOC freeing the main thread from blocking the UI. Unfortunately I seem to be running into a couple of situations that cause deadlocks.

    If the child MOC (on the main thread) is faulting in an object and the parent context is sent an -executeFetchRequest: or -save: it always deadlocks. Both operations are done within the context of a performBlock: for their respective MOCs although the docs seem to indicate that using a main thread concurrency type MOC on the main thread without performBlock: is fine.

    It appears that the private queue is waiting on the PSCs lock which the child context on the main thread has already locked. It appears that the child context (while holding the PSCs lock) is trying to dispatch_sync to the parent context and thus they are both waiting for each other.

    Is PriveQueue -> MainQueue a supported configuration? It seems most people still have the parent context on the main thread.

    The main thread looks like this:

    #0    0x960f6c5e in semaphore_wait_trap ()
    #1    0x04956bb5 in _dispatch_thread_semaphore_wait ()
    #2    0x04955c8f in _dispatch_barrier_sync_f_slow ()
    #3    0x04955dea in dispatch_barrier_sync_f ()
    #4    0x01797de5 in _perform ()
    #5    0x01798547 in -[NSManagedObjectContext(_NestedContextSupport) newValuesForObjectWithID:withContext:error:] ()
    #6    0x0176416b in _PFFaultHandlerLookupRow ()
    #7    0x01763f97 in -[NSFaultHandler fulfillFault:withContext:forIndex:] ()
    #8    0x01763b75 in _PF_FulfillDeferredFault ()
    #9    0x017639f2 in _sharedIMPL_pvfk_core ()
    #10    0x017681a0 in _pvfk_11 ()
    #11    0x0001b322 in -[FBUser sectionName] at /Users/mlink/Code/x/x/FBUser.m:62
    #12    0x011a8813 in _NSGetUsingKeyValueGetter ()
    #13    0x017a0652 in -[NSManagedObject valueForKey:] ()
    #14    0x011ab8d5 in -[NSObject(NSKeyValueCoding) valueForKeyPath:] ()
    #15    0x01851f72 in -[NSFetchedResultsController(PrivateMethods) _sectionNameForObject:] ()
    #16    0x01853af6 in -[NSFetchedResultsController(PrivateMethods) _computeSectionInfo:error:] ()
    #17    0x01850ea6 in -[NSFetchedResultsController performFetch:] ()
    #18    0x0003a4fc in __62-[SYFriendsTableViewController updateFetchedResultsController]_block_invoke_0 ()
    #19    0x01797af3 in developerSubmittedBlockToNSManagedObjectContextPerform ()
    #20    0x049554f0 in _dispatch_main_queue_callback_4CF ()
    #21    0x01b3e833 in __CFRunLoopRun ()
    #22    0x01b3ddb4 in CFRunLoopRunSpecific ()
    #23    0x01b3dccb in CFRunLoopRunInMode ()
    #24    0x023d6879 in GSEventRunModal ()
    #25    0x023d693e in GSEventRun ()
    #26    0x0089aa9b in UIApplicationMain ()
    #27    0x00002656 in main at /Users/mlink/Code/x/x/main.mm:16

    the private queue stack looks like this:

    #0    0x960f8876 in __psynch_mutexwait ()
    #1    0x97e9e6af in pthread_mutex_lock ()
    #2    0x0172ec22 in -[_PFLock lock] ()
    #3    0x0172ebfa in -[NSPersistentStoreCoordinator lock] ()
    #4    0x01746a8c in -[NSManagedObjectContext(_NSInternalAdditions) lockObjectStore] ()
    #5    0x01745030 in -[NSManagedObjectContext executeFetchRequest:error:] ()
    #6    0x0009d49f in -[NSManagedObjectContext(Additions) executeFetchRequest:] at /Users/mlink/Code/objc/C/C/NSManagedObjectContext+Additions.m:44
    #7    0x0002177f in +[FBUser usersForFbids:inManagedObjectContext:] at /Users/mlink/Code/x/x/FBUser.m:435
    #8    0x00021fc0 in __77+[FBUser updateUserFromGraphValues:inManagedObjectContext:completionHandler:]_block_invoke_0 at /Users/mlink/Code/x/x/FBUser.m:461
    #9    0x0180f9f3 in developerSubmittedBlockToNSManagedObjectContextPerform_privateasync ()
    #10    0x04954ecf in _dispatch_queue_drain ()
    #11    0x04954d28 in _dispatch_queue_invoke ()
    #12    0x049544af in _dispatch_worker_thread2 ()
    #13    0x97ea1b24 in _pthread_wqthread ()
    #14    0x97ea36fe in start_wqthread ()
  • On Jan 13, 2012, at 12:39 AM, Michael Link wrote:

    > the docs seem to indicate that using a main thread concurrency type MOC on the main thread without performBlock: is fine.

    It is, otherwise you couldn't (say) bind to a managed object you fetched via a main-queue context on OS X.  What OS version(s) are you seeing this with?

    > It appears that the private queue is waiting on the PSCs lock which the child context on the main thread has already locked. It appears that the child context (while holding the PSCs lock) is trying to dispatch_sync to the parent context and thus they are both waiting for each other.
    >
    > Is PriveQueue -> MainQueue a supported configuration? It seems most people still have the parent context on the main thread.

    Using a parent private-queue context and a child main-queue context is actually a good design.  I'm not sure about your deadlock; does it still occur if you don't use -performBlock: on your main-queue context but just interact with it directly?

      -- Chris
  • On Jan 13, 2012, at 3:45 PM, Chris Hanson wrote:

    > On Jan 13, 2012, at 12:39 AM, Michael Link wrote:
    >
    >> the docs seem to indicate that using a main thread concurrency type MOC on the main thread without performBlock: is fine.
    >
    > It is, otherwise you couldn't (say) bind to a managed object you fetched via a main-queue context on OS X.  What OS version(s) are you seeing this with?

    iOS 5.0 on simulator and iOS 5.0.1 on iPhone.

    >
    >> It appears that the private queue is waiting on the PSCs lock which the child context on the main thread has already locked. It appears that the child context (while holding the PSCs lock) is trying to dispatch_sync to the parent context and thus they are both waiting for each other.
    >>
    >> Is PriveQueue -> MainQueue a supported configuration? It seems most people still have the parent context on the main thread.
    >
    > Using a parent private-queue context and a child main-queue context is actually a good design.  I'm not sure about your deadlock; does it still occur if you don't use -performBlock: on your main-queue context but just interact with it directly?

    Yes, in exactly the same way. I'm starting to think the problem is with NSFetchedResultsController which is always stuck at performFetch: when these deadlocks occur. Most of the time it will be stuck trying to fault in an object as a result of asking for it's section name. As a test I tried to reproduce what the FRC does and performed the executeFetchRequest: and then iterated through the results asking each object for it's section name. And this doesn't cause a deadlock. If I leave the FRC to do performFetch: after I do my test it will still deadlock there. I'm 99% sure that the FRC has a synchronization problem with nested contexts.

    --
    Michael
previous month january 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today