Reproducible Core Data Deadlock: FRC and sectionNameKeyPath (iOS 8 and iOS 9)

Originator:michael.gorbach
Number:rdar://21440887 Date Originated:6/18/15
Status:Open Resolved:
Product:iOS SDK Product Version:iOS 8 and iOS 9
Classification: Reproducible:
 
Sample:
http://cloud.mgorbach.name/181W272I2k2a

Summary:
We have found a reproducible CD deadlock that can occur when using a fetched results controller with a "complex" (i.e. either multi-level or "synthesized" (not in the database) sectionNameKeyPath). This occurs specifically with a stack that has a top-level background (private queue) writing context, where the main thread context is a child of this context. 
Changes to CD (object creations and updates) as well as fetch requests, are taking place sequentially on background "import" contexts (private queue contexts). These contexts are also children of the top-level private queue writing context. If, while these CD changes and fetch requests are taking place in the background, the main thread Fetched Results Controller is asked to perform it's initial fetch, a deadlock results.

The deadlock appears to occur as follows:

On the main thead:
Fetched Results Controller executes fetchRequest
Fetch Request execution locks access main thread MOC
performs _computeSectionInfo:error:
_computeSectionInfo:error: takes the main-thread MOC lock again (OK because it is re-entrant)
_computeSectionInfo takes the PSC lock (via performBlockAndWait: on the PSC)
_computeSectionInfo does its work
calls newValuesForObjectWithID:withContext:error:, which appears to require a lock on the parent context (the top-level private queue context)
newValuesForObjectWithID waits on the parent context lock

On some background thread:
A Fetch Request is being executed
executeFetchRequest: takes a lock on the background import context
executeFetchRequest:withContext:error takes a lock on parent context
calls _parentObjectsForFetchRequest:inContext:error:
calls executeFetchRequest: for root / parent writing context (private queue context)
executeFetchRequest waits on the PSC lock to do its work

In summary:
The main thread, having acquired the PSC lock, waits for the lock on the root writer context
The background thread, having acquired a lock on the root writer context, waits for the PSC lock

This looks like a situation where the locks are acquired in different order, and therefore a deadlock results.

Workaround:
This issue appears to have to do specifically with whether an object is faulted in during the fetch request on the main thread. If the objects being retrieved are faults at the time, everything is OK. If they have already been faulted in, we get this deadlock. Resetting the objects immediately prior to calling fetch on the FRC appears to work around the issue. (see commented out code)

Note:
Regression from iOS 7. This sample code does not deadlock on iOS 7.

Note:
Deadlock still occurs on iOS 9.

Steps to Reproduce:
See description for the scenario.
To reproduce, run attached sample project.
Notice deadlock.

Uncomment the "refresh / merge changes" code in the second FRC fetch.
Notice that deadlock goes away.

Expected Results:
No deadlock in either case.

Actual Results:
Deadlock.

Version:
iOS 8 and iOS 9

Notes:

Configuration:
Simulator or Device, iOS 8 and iOS 9

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!