Deleting object in background moc then refreshing it in main moc causes crash in NSFetchedResultsController update
Asked Answered
C

2

2

I encountered an NSObjectInaccessibleException that I can not understand:

 *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x8c40040 <x-coredata://85B02C1C-1FFC-4CBF-B7AC-EEA259115427/Event/p6>''
*** First throw call stack:
(
    0   CoreFoundation                      0x01aa15e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x018248b6 objc_exception_throw + 44
    2   CoreData                            0x0025733b _PFFaultHandlerLookupRow + 2715
    3   CoreData                            0x00256897 -[NSFaultHandler fulfillFault:withContext:forIndex:] + 39
    4   CoreData                            0x00256473 _PF_FulfillDeferredFault + 259
    5   CoreData                            0x002562c6 _sharedIMPL_pvfk_core + 70
    6   CoreData                            0x0025acd5 -[NSManagedObject(_PFDynamicAccessorsAndPropertySupport) _genericValueForKey:withIndex:flags:] + 85
    7   CoreData                            0x00294781 _PF_Handler_Public_GetProperty + 161
    8   CoreData                            0x00294685 -[NSManagedObject valueForKey:] + 149
    9   Foundation                          0x01471a5a -[NSObject(NSKeyValueCoding) valueForKeyPath:] + 409
    10  Foundation                          0x015079ba -[NSSortDescriptor compareObject:toObject:] + 166
    11  CoreData                            0x00366175 +[NSFetchedResultsController(PrivateMethods) _insertIndexForObject:inArray:lowIdx:highIdx:sortDescriptors:] + 309
    12  CoreData                            0x00360f61 -[NSFetchedResultsController(PrivateMethods) _postprocessInsertedObjects:] + 737
    13  CoreData                            0x00362c44 -[NSFetchedResultsController(PrivateMethods) _postprocessUpdatedObjects:] + 916
    14  CoreData                            0x00363578 -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 2152
    15  Foundation                          0x014f7bf9 __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke + 40
    16  CoreFoundation                      0x01afd524 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
    17  CoreFoundation                      0x01a5500b _CFXNotificationPost + 2859
    18  Foundation                          0x01431951 -[NSNotificationCenter postNotificationName:object:userInfo:] + 98
    19  CoreData                            0x00268173 -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 83
    20  CoreData                            0x0030778f -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] + 367
    21  CoreData                            0x00263e38 -[NSManagedObjectContext(_NSInternalChangeProcessing) _postRefreshedObjectsNotificationAndClearList] + 136
    22  CoreData                            0x002639e4 -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 3140
    23  CoreData                            0x00262d99 -[NSManagedObjectContext processPendingChanges] + 41
    24  CoreData                            0x00236fe1 _performRunLoopAction + 321
    25  CoreFoundation                      0x01a694ce __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
    26  CoreFoundation                      0x01a6941f __CFRunLoopDoObservers + 399
    27  CoreFoundation                      0x01a47344 __CFRunLoopRun + 1076
    28  CoreFoundation                      0x01a46ac3 CFRunLoopRunSpecific + 467
    29  CoreFoundation                      0x01a468db CFRunLoopRunInMode + 123
    30  GraphicsServices                    0x039029e2 GSEventRunModal + 192
    31  GraphicsServices                    0x03902809 GSEventRun + 104
    32  UIKit                               0x00592d3b UIApplicationMain + 1225
    33  RefreshDeletedTest                  0x00005d9d main + 141
    34  libdyld.dylib                       0x020df70d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException

It is caused by a specific code flow:

- (void)deleteObject:(NSManagedObject *)object {
    NSManagedObjectID *objID = object.objectID;
    NSManagedObjectContext *dataProcessingMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    dataProcessingMoc.persistentStoreCoordinator = self.managedObjectContext.persistentStoreCoordinator;
    dataProcessingMoc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataProcessingContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:dataProcessingMoc];
    // If I use performBlockAndWait: instead of performBlock: the issue goes away.
    [dataProcessingMoc performBlock:^{
        NSManagedObject *objInDPMoc = [dataProcessingMoc objectWithID:objID];
        NSLog(@"Delete: %@", objInDPMoc);
        [dataProcessingMoc deleteObject:objInDPMoc]; // 1
        [object.managedObjectContext performBlockAndWait:^{
            [object.managedObjectContext refreshObject:object mergeChanges:NO]; // 2
        }];
        [dataProcessingMoc save:NULL]; // 3
    }];
}

- (void)dataProcessingContextDidSave:(NSNotification *)note {
    NSManagedObjectContext *moc = self.managedObjectContext;
    [moc performBlockAndWait:^{ // 3
        [moc mergeChangesFromContextDidSaveNotification:note];
        NSError *error;
        if ([moc hasChanges] && ![moc save:&error]) {
            NSLog(@"Unresolved error %@", error);
            abort();
        }
    }];
}

Note: the code must look weird and does not make much sense. It is the result of my reducing real code to the minimum bug-reproducing code.

  1. Delete an object in background moc.
  2. Refresh the same object to turned into a fault in main moc.
  3. Save background moc and merge its changes to main moc using NSManagedObjectContextDidSaveNotification.

The exception occurs before the real merge, at the main moc's call of performBlockAndWait:.

Here is the background moc's thread stack at the time of crash:

#0  0x02210fb6 in semaphore_wait_trap ()
#1  0x01e3ccde in _dispatch_thread_semaphore_wait ()
#2  0x01e3a516 in _dispatch_barrier_sync_f_slow ()
#3  0x01e3a413 in dispatch_barrier_sync_f ()
#4  0x0028bf1f in -[NSManagedObjectContext performBlockAndWait:] ()
#5  0x000037b5 in -[MasterViewController dataProcessingContextDidSave:] at /Users/an0/dev/Projects/iOS/Study/RefreshDeletedTest/RefreshDeletedTest/MasterViewController.m:84
#6  0x014f7bf9 in __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke ()
#7  0x01afd524 in __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ ()
#8  0x01a5500b in _CFXNotificationPost ()
#9  0x01431951 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
#10 0x0027bb2c in -[NSManagedObjectContext(_NSInternalAdditions) _didSaveChanges] ()
#11 0x0026720f in -[NSManagedObjectContext save:] ()
#12 0x0000345a in __37-[MasterViewController cleanObjects:]_block_invoke at /Users/an0/dev/Projects/iOS/Study/RefreshDeletedTest/RefreshDeletedTest/MasterViewController.m:78
#13 0x003047c3 in developerSubmittedBlockToNSManagedObjectContextPerform_privateasync ()
#14 0x01e4d4b0 in _dispatch_client_callout ()
#15 0x01e3b07f in _dispatch_queue_drain ()
#16 0x01e3ae7a in _dispatch_queue_invoke ()
#17 0x01e3be1f in _dispatch_root_queue_drain ()
#18 0x01e3c137 in _dispatch_worker_thread2 ()
#19 0x021d9dab in _pthread_wqthread ()

Why it crashes this way? Is my code really wrong or it is a bug of Core Data?

Another odd thing that I can not understand is that, after such a crash the app can not be launch any more for it will crash on launch:

2014-01-05 12:31:34.151 RefreshDeletedTest[3546:70b] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xa88d060 <x-coredata://93009ECF-EC6E-4B12-BC40-997C4BF3B8DF/Event/p11>''
*** First throw call stack:
(
    0   CoreFoundation                      0x01aa05e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x018238b6 objc_exception_throw + 44
    2   CoreData                            0x0025633b _PFFaultHandlerLookupRow + 2715
    3   CoreData                            0x00255897 -[NSFaultHandler fulfillFault:withContext:forIndex:] + 39
    4   CoreData                            0x00255473 _PF_FulfillDeferredFault + 259
    5   CoreData                            0x002552c6 _sharedIMPL_pvfk_core + 70
    6   CoreData                            0x00259cd5 -[NSManagedObject(_PFDynamicAccessorsAndPropertySupport) _genericValueForKey:withIndex:flags:] + 85
    7   CoreData                            0x00293781 _PF_Handler_Public_GetProperty + 161
    8   CoreData                            0x00293685 -[NSManagedObject valueForKey:] + 149
    9   Foundation                          0x014a8f0a -[NSArray(NSKeyValueCoding) valueForKey:] + 456
    10  Foundation                          0x01470a5a -[NSObject(NSKeyValueCoding) valueForKeyPath:] + 409
    11  Foundation                          0x0149b99d -[NSArray(NSKeyValueCoding) valueForKeyPath:] + 580
    12  CoreData                            0x0035b7e9 -[NSFetchedResultsController performFetch:] + 697
    13  RefreshDeletedTest                  0x00003910 -[MasterViewController fetchedResultsController] + 800
    14  RefreshDeletedTest                  0x0000307e -[MasterViewController numberOfSectionsInTableView:] + 78
    15  UIKit                               0x007e5b94 -[UITableViewRowData(UITableViewRowDataPrivate) _updateNumSections] + 102
    16  UIKit                               0x007e6993 -[UITableViewRowData invalidateAllSections] + 69
    17  UIKit                               0x0065d237 -[UITableView _updateRowData] + 194
    18  UIKit                               0x0065d170 -[UITableView _ensureRowDataIsLoaded] + 45
    19  UIKit                               0x006704b3 -[UITableView numberOfSections] + 35
    20  UIKit                               0x0084d3d3 -[UITableViewController viewWillAppear:] + 103
    21  UIKit                               0x006a8bfa -[UIViewController _setViewAppearState:isAnimating:] + 419
    22  UIKit                               0x006a9108 -[UIViewController __viewWillAppear:] + 114
    23  UIKit                               0x006cb3f5 -[UINavigationController _startTransition:fromViewController:toViewController:] + 800
    24  UIKit                               0x006cc09c -[UINavigationController _startDeferredTransitionIfNeeded:] + 645
    25  UIKit                               0x006cccb9 -[UINavigationController __viewWillLayoutSubviews] + 57
    26  UIKit                               0x00806181 -[UILayoutContainerView layoutSubviews] + 213
    27  UIKit                               0x0e35656f -[UILayoutContainerViewAccessibility(SafeCategory) layoutSubviews] + 50
    28  UIKit                               0x005fc267 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 355
    29  libobjc.A.dylib                     0x0183581f -[NSObject performSelector:withObject:] + 70
    30  QuartzCore                          0x045802ea -[CALayer layoutSublayers] + 148
    31  QuartzCore                          0x045740d4 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380
    32  QuartzCore                          0x04580235 -[CALayer layoutIfNeeded] + 160
    33  UIKit                               0x006b7613 -[UIViewController window:setupWithInterfaceOrientation:] + 304
    34  UIKit                               0x005d6177 -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 5212
    35  UIKit                               0x005d4d16 -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:] + 82
    36  UIKit                               0x005d4be8 -[UIWindow _setRotatableViewOrientation:updateStatusBar:duration:force:] + 117
    37  UIKit                               0x005d4c70 -[UIWindow _setRotatableViewOrientation:duration:force:] + 67
    38  UIKit                               0x005d3d0a __57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 120
    39  UIKit                               0x005d3c6c -[UIWindow _updateToInterfaceOrientation:duration:force:] + 400
    40  UIKit                               0x005d49c3 -[UIWindow setAutorotates:forceUpdateInterfaceOrientation:] + 870
    41  UIKit                               0x005d7fb6 -[UIWindow setDelegate:] + 449
    42  UIKit                               0x006a9737 -[UIViewController _tryBecomeRootViewControllerInWindow:] + 180
    43  UIKit                               0x005cdc1c -[UIWindow addRootViewControllerViewIfPossible] + 609
    44  UIKit                               0x005cdd97 -[UIWindow _setHidden:forced:] + 312
    45  UIKit                               0x005ce02d -[UIWindow _orderFrontWithoutMakingKey] + 49
    46  UIKit                               0x0e332c66 -[UIWindowAccessibility(SafeCategory) _orderFrontWithoutMakingKey] + 77
    47  UIKit                               0x005d889a -[UIWindow makeKeyAndVisible] + 65
    48  UIKit                               0x0058bcd0 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1851
    49  UIKit                               0x005903a8 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 824
    50  UIKit                               0x005a487c -[UIApplication handleEvent:withNewEvent:] + 3447
    51  UIKit                               0x005a4de9 -[UIApplication sendEvent:] + 85
    52  UIKit                               0x00592025 _UIApplicationHandleEvent + 736
    53  GraphicsServices                    0x039032f6 _PurpleEventCallback + 776
    54  GraphicsServices                    0x03902e01 PurpleEventCallback + 46
    55  CoreFoundation                      0x01a1bd65 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
    56  CoreFoundation                      0x01a1ba9b __CFRunLoopDoSource1 + 523
    57  CoreFoundation                      0x01a4677c __CFRunLoopRun + 2156
    58  CoreFoundation                      0x01a45ac3 CFRunLoopRunSpecific + 467
    59  CoreFoundation                      0x01a458db CFRunLoopRunInMode + 123
    60  UIKit                               0x0058fadd -[UIApplication _run] + 840
    61  UIKit                               0x00591d3b UIApplicationMain + 1225
    62  RefreshDeletedTest                  0x0000508d main + 141
    63  libdyld.dylib                       0x020de70d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException

Here is the sample project that you can run to see the issues yourself: http://d.pr/f/y3VQ

Collie answered 5/1, 2014 at 18:35 Comment(6)
What happens if you comment [object.managedObjectContext refreshObject:object mergeChanges:NO];?Samathasamau
@flexaddicted everything would be normal. But meaningless for me because it is what I want to do.Collie
What you expect from that call?Samathasamau
Turn it into fault to trim object graph in memory, in my real project. You can not read it from the code above because it is a reduced-to-minmun code.Collie
What is the reason for deleting the object(you already have on the main thread) on a background thread? Just curious as this seems to be a low of additional overhead.Supersensual
@DuncanGroenewald potentially time consuming data clean-up.Collie
W
5

In a multithreaded environment there is no way to avoid this issue.

As long as you have 2 different contexts on the same hierarchy level (be it parent child architecture or directly connected to the store) you might encounter a situation where one context deleted an object (and committed changes to the store) and the other one try to access this object before changes were merged.
This might also happen when parent context commit changes to the store and its child try to access deleted items he hold as faults.
In general, in every place where changes are not propagated automatically between contexts prior to a commit, there is a chance to get this exception.

If you can pipeline your contexts (store-context1-context2-...) and only delete from the final context in the chain you can avoid this issue.

You can devise some algorithm to delay your delete operations so they are committed only when no other context use a specific item, but this involves a lot of bookkeeping on your part.

You can reduce conflicts between your context by fetching full fledged (no faults) objects, or the entire sub-graph you will be working on (cannot be done when using FRC) and make your operations on these objects.

Welborn answered 5/1, 2014 at 19:15 Comment(8)
I know the general pattern of this kind of issues. But what's going on for my case specifically? And what about the launch failure later?Collie
in your case you hold a fault of an object that no longer exist in the store and your main context try to fulfil a fault for it before changes were merged to it (by the FRC).Welborn
I don't hold the object myself. The FRC holds it. The interesting part of my case is: if I don't turn object into fault in main moc, FRC certainly is able to handle the background deletion of object, so it seams turning object into fault causes some problem for FRC in handling the background deletion, which I blame to Core Data and suspect it is a bug of it because in my opinion FRC should be able to handle it.Collie
@Collie This is not a bug in CoreData, but a result of a multithreaded environment. there is a race between the merge of the changes from one context and the access to faulted resources in the other. since FRC decides when to turn an object into a fault or fault in an object, there will be a problem if it will try and access an object that was deleted in the background. see my final paragraph (if an object was faulted in and then accessed by the FRC it is fine cause no faults are fired)Welborn
I understand your reasoning. So can we say it is a weak point of FRC? I know FRC can not handle unknown deletion behind its back, so I use mergeChangesFromContextDidSaveNotification to notify it. But it crashes on the way to merge. That's where I think it fails me. I manually re-fault objects because FRC can not break reference cycles itself. Maybe I should ask another related question: How to properly trim object graph brought in by NSFetchedResultsController.Collie
@Collie I agree that CoreData could have handled an unfulfilled fault more gracefully. However, FRC handle memory management for your (faulting and refreshing objects as needed) so there is no need for you to refresh objects fetched by the FRC manually, even with retain cycles (AFAIK).Welborn
Are you sure FRC re-faults objects even with reference cycles? I believe FRC could do it but I'm not sure whether it really does it. For example, say the model is note<-->>image, and I have a FRC of notes supporting a table view. When some notes scrolls out of view, FRC could re-fault these notes. But these notes have images referring to them, will FRC ignore this fact and re-fault these notes regardlessly?Collie
I've just tested it, and the FRC will break reference cycles for its fetched objects. I mean by that that the FRC will turn into fault (refresh the object) fetched by the defined request. this in turn will break retain cycles and release the referenced objects (as long as you don't keep a reference to them yourself).Welborn
B
0

This was addressed in WWDC 2015 220 What's New in Core Data with release of iOS 9 and the shouldDeleteInaccessibleFaults property on NSManagedObjectContext.

Currently, shouldDeleteInaccessibleFaults defaults to yes. If we encounter a fault we'll mark the fault as deleted and any missing attributes will be null, nil or zero. This allows your app to continue on with this object and treat it as a deleted object. No longer will you crash but you'll be able to merely keep going on and show the user what they have expected to see.

Briefcase answered 27/4, 2020 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.