PerformBlockAndWait In a child context with closed oval queues Parent on iOS 7

I have two NSManagedObjectContext named importContext and childContext . childContext is a child of importContext , and both are NSPrivateQueueConcurrencyType .

To remove things from the main thread, I do a ton of work in the importContext queue. This job involves a lot of fetching and saving, so it’s convenient to wrap it all inside performBlockAndWait: importContext (this is necessary for a synchronous operation, because the code I received after performBlockAndWait depends on its results).

At some point during this work, I may need to create new managed objects from the JSON results. These JSON values ​​may not be valid and may not perform my checks, so after I create the objects, I need to be able to break them if they are not good. Here comes the childContext . I insert my new object into it, and if its JSON attributes do not make sense, I interrupt childContext .

The problem occurs when I need to save a childContext . I expect him to have his own private queue separate from the parent queue. However, this causes a deadlock ONLY on iOS 7 (not iOS 8). When I run the same code on simulators and iOS 8 devices, childContext creates its own queue in a separate thread and saves correctly.

It seems that when I launch iOS 7, childContext tries to make save: in the parent queue, but the parent is waiting for its child, which causes a dead end. In iOS 8, this does not happen. Does anyone know why?

Here is the simplified code:

  -(NSManagedObjectContext *)importContext { NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; moc.persistentStoreCoordinator = [self storeCoordinator]; return moc; } -(void)updateItems:(NSArray*)ItemDescriptions { [self.importContext performBlockAndWait:^{ //get info and update ... ... if(needToCreateNewItem){ NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.importedContext; //Insert and create new item ... [childContext performBlockAndWait:^{ id newObject = [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:childContext]; }]; ... // Do something with this object if([newObject isReadyToSave]) __block NSError* e = nil; __block BOOL saveSucceeded = NO; [childContext performBlockAndWait:^{ saveSucceeded = [childContext save:&e]; // DEADLOCK ON iOS 7!!!! }]; } .... } }]; } 

A simple job is working on a separate dispatch queue (instead of the importContext queue), but the reason I ask this question is because I want to understand the reason why this happens. I think that the preservation of the child should occur only in its own queue.

UPDATE 1

Re. Marcus questions:

  • updateItems: called from NSInvocationOperation in the operation queue, so it disconnects from the main queue.

  • In iOS 7, I can pause the application at any time and view the stack, and the context queue of the managed object will be blocked:

     (lldb) bt * thread #7: tid = 0xed07, 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8, queue = 'NSManagedObjectContext Queue' frame #0: 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8 frame #1: 0x385bbbac libsystem_platform.dylib`_os_semaphore_wait + 12 frame #2: 0x3848461a libdispatch.dylib`_dispatch_barrier_sync_f_slow + 138 frame #3: 0x2d4f3df2 CoreData`_perform + 102 frame #4: 0x2d4fe1ac CoreData`-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 240 frame #5: 0x2d492f42 CoreData`-[NSManagedObjectContext save:] + 826 * frame #6: 0x000c1c96 DBDevApp`__69+[DBManagedObject createWithAttributes:inManagedObjectContext:error:]_block_invoke77(.block_descriptor=<unavailable>) + 118 at DBManagedObject.m:117 frame #7: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 88 frame #8: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #9: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #10: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #11: 0x000c1916 DBDevApp`+[DBManagedObject createWithAttributes:inManagedObjectContext:error:](self=0x005c1790, _cmd=0x0054a033, attributes=0x188e context=0x17500800, error=0x02e68ae8) + 658 at DBManagedObject.m:116 frame #12: 0x000fe138 DBDevApp`-[DBAPIController createOrUpdateItems:withIDs:IDKeys:ofClass:amongExistingItems:withFindByIDPredicate:](self=0x17775de0, _cmd=0x0054de newItemDescriptions=0x188eada0, itemIDs=0x18849580, idKey=0x0058e290, class=0x005c1790, existingItems=0x1756b560, findByID=0x18849c80) + 2472 at DBAPIController.m:972 frame #13: 0x00100ca0 DBDevApp`__39-[DBAPIController updatePatientGroups:]_block_invoke(.block_descriptor=0x02e68ce0) + 476 at DBAPIController.m:1198 frame #14: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform frame #15: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #16: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #17: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #18: 0x00100a96 DBDevApp`-[DBAPIController updatePatientGroups:](self=0x17775de0, _cmd=0x0054dfcd, groupsArray=0x188eada0) + 214 at DBAPIController.m:1191 frame #19: 0x2d721584 CoreFoundation`__invoking___ + 68 frame #20: 0x2d66c0da CoreFoundation`-[NSInvocation invoke] + 282 frame #21: 0x2e0f3d2c Foundation`-[NSInvocationOperation main] + 112 frame #22: 0x2e0515aa Foundation`-[__NSOperationInternal _start:] + 770 frame #23: 0x2e0f576c Foundation`__NSOQSchedule_f + 60 frame #24: 0x38484f10 libdispatch.dylib`_dispatch_queue_drain$VARIANT$mp + 488 frame #25: 0x38484c96 libdispatch.dylib`_dispatch_queue_invoke$VARIANT$mp + 42 frame #26: 0x38485a44 libdispatch.dylib`_dispatch_root_queue_drain + 76 frame #27: 0x38485d28 libdispatch.dylib`_dispatch_worker_thread2 + 56 frame #28: 0x385c0bd2 libsystem_pthread.dylib`_pthread_wqthread + 298 : IDKeys: ofClass: amongExistingItems: withFindByIDPredicate:] (self = 0x17775de0, _cmd = 0x0054de newItemDescriptions = 0x188eada0, itemIDs = 0x18849580, idKey = 0x0058e290, class = 0x005c1790, existingItems = 0x1756b560 (lldb) bt * thread #7: tid = 0xed07, 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8, queue = 'NSManagedObjectContext Queue' frame #0: 0x38546aa8 libsystem_kernel.dylib`semaphore_wait_trap + 8 frame #1: 0x385bbbac libsystem_platform.dylib`_os_semaphore_wait + 12 frame #2: 0x3848461a libdispatch.dylib`_dispatch_barrier_sync_f_slow + 138 frame #3: 0x2d4f3df2 CoreData`_perform + 102 frame #4: 0x2d4fe1ac CoreData`-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 240 frame #5: 0x2d492f42 CoreData`-[NSManagedObjectContext save:] + 826 * frame #6: 0x000c1c96 DBDevApp`__69+[DBManagedObject createWithAttributes:inManagedObjectContext:error:]_block_invoke77(.block_descriptor=<unavailable>) + 118 at DBManagedObject.m:117 frame #7: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 88 frame #8: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #9: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #10: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #11: 0x000c1916 DBDevApp`+[DBManagedObject createWithAttributes:inManagedObjectContext:error:](self=0x005c1790, _cmd=0x0054a033, attributes=0x188e context=0x17500800, error=0x02e68ae8) + 658 at DBManagedObject.m:116 frame #12: 0x000fe138 DBDevApp`-[DBAPIController createOrUpdateItems:withIDs:IDKeys:ofClass:amongExistingItems:withFindByIDPredicate:](self=0x17775de0, _cmd=0x0054de newItemDescriptions=0x188eada0, itemIDs=0x18849580, idKey=0x0058e290, class=0x005c1790, existingItems=0x1756b560, findByID=0x18849c80) + 2472 at DBAPIController.m:972 frame #13: 0x00100ca0 DBDevApp`__39-[DBAPIController updatePatientGroups:]_block_invoke(.block_descriptor=0x02e68ce0) + 476 at DBAPIController.m:1198 frame #14: 0x2d4f6934 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform frame #15: 0x3847e81e libdispatch.dylib`_dispatch_client_callout + 22 frame #16: 0x384847ca libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 26 frame #17: 0x2d4f6a72 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 106 frame #18: 0x00100a96 DBDevApp`-[DBAPIController updatePatientGroups:](self=0x17775de0, _cmd=0x0054dfcd, groupsArray=0x188eada0) + 214 at DBAPIController.m:1191 frame #19: 0x2d721584 CoreFoundation`__invoking___ + 68 frame #20: 0x2d66c0da CoreFoundation`-[NSInvocation invoke] + 282 frame #21: 0x2e0f3d2c Foundation`-[NSInvocationOperation main] + 112 frame #22: 0x2e0515aa Foundation`-[__NSOperationInternal _start:] + 770 frame #23: 0x2e0f576c Foundation`__NSOQSchedule_f + 60 frame #24: 0x38484f10 libdispatch.dylib`_dispatch_queue_drain$VARIANT$mp + 488 frame #25: 0x38484c96 libdispatch.dylib`_dispatch_queue_invoke$VARIANT$mp + 42 frame #26: 0x38485a44 libdispatch.dylib`_dispatch_root_queue_drain + 76 frame #27: 0x38485d28 libdispatch.dylib`_dispatch_worker_thread2 + 56 frame #28: 0x385c0bd2 libsystem_pthread.dylib`_pthread_wqthread + 298 

The code shown above was a simplified version. The part in which I create a new child context is inside a class called DBManagedObject . Here is a screenshot of the entire package:

enter image description here

Update 2 - DBManagedObject Explanation

DBManagedObject is the base class for all of my core data classes. It mainly handles conversion to and from JSON-parsed dictionaries. It has 3 main methods: +createWithAttributes:inManagedObjectContext:error: -updateWithAttributes:error: and attributes .

  • +createWithAttributes:inManagedObjectContext:error: creates a child context of the provided context of the managed object, inserts a new object into the child context, and calls updateWithAttributes:error: on this object. If the update is successful (i.e., all the values ​​that we want to set on this object make sense), it saves the child context, receives a link to the new object in the MOC, which is included as a parameter, and returns this link:

     NSManagedObjectContext* childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = context; __block id newObject; [childContext performBlockAndWait:^{ newObject = [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:childContext]; }]; if ([newObject updateWithAttributes:attributes error:error]) { NSError* e = nil; if ([childContext save:&e]) { id parentContextObject = [context objectWithID:[(NSManagedObject*)newObject objectID]]; return parentContextObject; } else { if (error != NULL) { *error = e; } return nil; } } else return nil; 
  • updateWithAttributes:error: does the heavy lifting of translation keys between JSON keys into the ones I used in my data model as properties of objects. (i.e., "first_name" becomes "firstName"). It also formats JSON values ​​if necessary (date strings become NSDate s). He also establishes a relationship.

+7
ios concurrency core-data nsmanagedobjectcontext
source share
2 answers

Who is calling -updateItems: :? If this happens in the main queue, you have a problem because you are blocking it.

Assuming this is not the case, can you share a stack thread with Xcode that shows the deadline? In particular, with the expansion of the queues and the expansion of the main queue?

I will update my answer as soon as I get a good look at this stack.

Quellish is correct that you insert the child incorrectly. Any work on this child MOC should be inside -performBlock: or -performBlockAndWait: I would extend -performBlockAndWait: to cover the entire creation and solution for the object, not just save.

Update 1

What does -createWithAttributes:inManagedObjectContext:error: do? This method seems to be doing something inappropriate. Maybe an attempt to force permanent identifiers to be set, or something else?

Update 2

As you know, your -createWithAttributes:inManagedObjectContext:error: is your problem. When you call -objectWithID: you call fetch so that it works completely on the NSPersistentStoreCoordinator , which in turn causes a lock.

In addition, this method does not help. There is absolutely no value in creating a context just to create an object, and then immediately capture the object in a different context. All harm, nothing good. Delete it completely and just create the object in the context in which you intend to use it. Save or discard it from the context in which it is used.

Do not be smart.

0
source share

From looking at your code, I see that you have 2 [childContext performBlockAndWait: ^ {which are nested. Removing one of them should fix the problem in ios7. The code is already running on this thread, which you do not need to do again.

Always check if you have nested execution blocks for the same context. This made my app stall earlier in ios7 and work in ios8

To check when you see a dead end, press the pause in the debugger and see what blocks all threads. Take a look at this specific code and check for nested blocks.

0
source share

All Articles