web-dev-qa-db-fra.com

Contenus d'objet géré imbriqués dans les données de base et blocages/gels fréquents

J'ai un problème qui est presque identique au problème décrit par cette personne ici, mais on ne m'a pas répondu:

http://www.cocoabuilder.com/archive/cocoa/312683-core-data-nested-managed-object-contexts-and-frequent-deadlocks.html#312683

Voici le problème:

J'ai une configuration MOC parent avec NSPrivateQueueConcurrencyType et un ensemble de coordinateur de magasin persistant, elle a une configuration MOC enfant avec NSMainQueueConcurrencyType. L'idée étant la majeure partie du travail long et des sauvegardes peut être effectuée sur le MOC privé, libérant ainsi le thread principal du blocage de l'interface utilisateur. Malheureusement, il semble que je me retrouve dans une ou deux situations qui provoquent des blocages.

Si le MOC enfant (sur le thread principal) effectue une extraction avec NSFetchedResultsController, le contexte parent reçoit un -executeFetchRequest: il peut créer un blocage. Les deux opérations sont effectuées dans le contexte d'un performBlock: pour leurs MOC respectifs, bien que la documentation semble indiquer que l'utilisation d'un type MOC de concurrence du thread principal sur le thread principal sans performBlock: va bien.

Il semble que la file d'attente privée attende le verrouillage du PSC dont le contexte enfant sur le thread principal a déjà été verrouillé. Il semble que le contexte enfant (tout en maintenant le verrou PSC) tente d’acheminer_sync au contexte parent et qu’ils s’attendent donc tous les deux.

PriveQueue -> MainQueue est-il une configuration prise en charge? Il semble que la plupart des gens ont toujours le contexte parent sur le fil principal.

Le fil principal ressemble à ceci:

> #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

la pile de files d'attente privée ressemble à ceci:

#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 ()

Il écrit aussi ceci:

Je commence à penser que le problème vient de NSFetchedResultsController, qui est toujours bloqué dans performFetch: lorsque ces blocages se produisent. La plupart du temps, il sera bloqué dans la tentative d'essayer de critiquer un objet après avoir demandé son nom de section. En guise de test, j'ai essayé de reproduire ce que fait le CRF et ai exécuté executeFetchRequest: puis itéré dans les résultats en demandant à chaque objet son nom de section. Et cela ne provoque pas une impasse. Si je quitte le CRF pour effectuer performFetch: après avoir fait mon test, il y aura toujours une impasse. Je suis sûr à 99% que le centre de ressources Français a un problème de synchronisation avec des contextes imbriqués.

Question: Quelqu'un sait-il pourquoi ce problème se produit? Savez-vous comment le résoudre? Est-ce un bug?

29
thejaz

Je viens de lire this SO post où fabrice truillot de chambrier recommande de ne pas utiliser les contextes imbriqués pour le moment. Il donne une référence à l'article Core Data Growing Pains .

De cet article:

NSFetchedResultsController deadlocks

Vous ne voulez jamais que votre application soit dans l'impasse. Avec NSFetchedResultsController et les contextes imbriqués, il est très facile à faire. En utilisant la même configuration UIManagedDocument décrite ci-dessus, l'exécution de demandes d'extraction dans le contexte de la file d'attente privée lors de l'utilisation de NSFetchedResultsController avec le contexte de la file d'attente principale risque de provoquer un blocage. Si vous commencez les deux à peu près au même moment, cela se produit avec une cohérence de presque 100%. NSFetchedResultsController est probablement en train d’obtenir un verrou qu’il ne devrait pas être. Cela a été rapporté comme étant corrigé pour une prochaine version d'iOS.

radar: // 11861499 corrigé dans une prochaine version

Cela semble décrire exactement votre problème.

34
Martin R

Pour mon application iOS 6, j'ai la même configuration d'accès simultané que l'OP: un MOC parent utilisant une file d'attente privée et un MOC enfant sur le thread principal. J'ai également un NSFetchedResultsController qui utilise le MOC enfant pour mettre à jour un UITableViewController. Ces deux MOC sont initialisés dans AppDelegate et doivent être utilisés dans l'application. AppDelegate a deux méthodes, savePrivateThreadMOCToCoreData et saveMainThreadMOCToCoreData , pour conserver les modifications apportées au CD. Au lancement, j’envoie un initialiseur coredata dans une file privée, comme suit. L'idée est de déposer immédiatement l'utilisateur dans la vue tabulaire et de permettre à l'initialiseur de mettre à jour les données de base en arrière-plan.

    dispatch_async(private_queue,^{
        [CoreDataInitializer initialize];
    });

Initialement, lorsque savePrivateThreadMOCToCoreData effectuait des sauvegardes dans un -performBlock, je voyais les mêmes impasses psynch_mutex décrites dans " Core Data Growing Pains " liées ci-dessus. J'ai également vu des plantages si j'essayais de lire des données dans la TableVC alors que la sauvegarde était en cours.

    Collection <__NSCFSet: 0x7d8ea90> was mutated while being enumerated.

Pour surmonter ces difficultés, je suis passé à la sauvegarde en utilisant -performBlockAndWait. J'ai arrêté de voir les blocages et les crashs, mais je ne me sentais pas bien de faire attendre les sauvegardes de l'interface utilisateur. Enfin, j'ai supprimé tous les appels de -performBlock * et utilisé une simple Vanilla [privateMOC save: & error] et, comme ça, tous mes problèmes ont disparu. Le contrôleur de résultats récupéré lit proprement les données partiellement sauvegardées et met à jour la table, sans plus d'erreurs de blocage ou d'erreurs "mutées en cours d'énumération".

Je soupçonne que -performBlock * est supposé être utilisé par autre threads, ceux qui n'ont pas créé le MOC en question, pour demander des opérations dessus. Étant donné que les MOC de mon fil principal et principal appartiennent au délégué de l'application, les sauvegardes sur le MOC privé ne doivent pas utiliser -performBlock *.

Il est probablement pertinent que, même si mon environnement de construction est iOS 6, mon SDK cible iOS 5.0 pour le déploiement de base. Il semble que les autres utilisateurs ne voient plus ce problème avec iOS 6.

2
Sai Ramachandran

Moi aussi j'ai eu un crash lié à developerSubmittedBlockToNSManagedObjectContextPerform .

Dans mon cas, considérons le modèle d'appel de méthode suivant:

[privatecontext performBlock:^{
    A(CDManager.privatecontext);
}];

où: A(CDManager.privateContext) appelle B() B() appelle C() C() appelle D ()

et: méthode A() et méthode C() contient certaines opérations Core Data. A() a déjà la connaissance du contexte sur lequel travailler, mais A() n'informe pas B() du contexte, et donc C() n'a pas non plus d'informations sur le contexte dans lequel travailler, donc C() fonctionne sur le contexte par défaut (principal). et cela provoque le plantage dû aux données de manière incohérente dans la base de données.

correctif: toutes les méthodes devant fonctionner sur les opérations de base de données sont paramétrées avec le contexte sur lequel elles doivent être utilisées, à l'exception de D() car elles ne doivent pas nécessairement fonctionner avec les opérations de base de données, comme:

A(context) calls B(context) B(context) calls C(context) C(context) calls D()

0
Firdous

J'ai résolu exactement le même problème avec les blocages causés par l'extraction simultanée à partir de deux threads (BG exécuté firedRequest, MAIN one extrait l'extraction de NSFRC). La solution consiste à créer un nouveau contexte pour la longue opération de synchronisation. Il n'a pas de contexte parent, il a le type de simultanéité NSPrivateQueueConcurrencyType et il est directement lié à un PSC commun. Après que tous les travaux longs aient été effectués dans ce contexte en arrière-plan, je l’enregistre et le fusionne avec une pile de contextes parallèles au repos à l’aide de la routine mergeChangesFromContextDidSaveNotification.

Une excellente solution est mise en œuvre dans Magical Record 3. Plus d’informations ici: https://stackoverflow.com/a/25060126/1885326 .

0
kas-kad

Cela m'arrive parce que les parents sont configurés avec NSMainQueueConcurencyType

Pour résoudre ce problème, je fais en sorte que le contexte d'objet géré de mainQueue soit un enfant. J'ai appelé reset à chaque fois que je voulais charger des choses pour que les données sur mainQueue soient identiques à celles du parent. Ce n'est souvent pas le cas.

0
user4951