CoreData crashed trying to run a pretty boring for...in loop.

Hi everyone, thanks very much for reading, and thank you in advance for any light you can shed on this.


The crash logs as "CRASH: *** -[NSMutableSet unionSet:]: set argument is not an NSSet". UnionSet is being called as part of a fast enumeration. As you see below the thing I'm trying to loop over claims to be a _NSFaultingMutableSet. Here are a few more fun facts.


  • This code is executing on a background thread using a NSManagedContext that is a child of the one from the main thread. I'm not new to core data threading, and this code has been in the wild for a while now and works for the rest of our users.
  • The background (child) context is never saved, and we don't care about keeping up to date with changes on the main thread so we aren't doing anything like merging in changes from other contexts.
  • "self" here is an instance of a Location, which is a subclass of NSManagedObject.
  • self.settings is a toMany relationship with another NSManagedObject subclass.
  • This is not the first odd thing core data has done to me lately ( see this other thread for more craziness: https://forums.developer.apple.com/thread/16392 ), so I might be unknowingly doing something odd somewhere else? Threading violation maybe? I've looked and not found anything.
  • iOS 9, XCode 7.1.1 (7B1005), OSX 10.11.1, Running on an iPad 2


Here's the offending code, crash happens on line 3:


- (LocationSetting*)settingForName:(NSString*)name {
    for(LocationSetting *setting in self.settings) { // the crash happened on this line.
        if(setting.name && [setting.name isEqualToString:name])
            return setting;
    }

    return nil;
}

Here's the data involved straight from the debugger (I've [redacted] customer info)


2015-11-11 15:16:57.801 Instore[1305:1533478] Received memory warning.
warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.
2015-11-11 15:19:39.315 Instore[1305:1533766] CRASH: *** -[NSMutableSet unionSet:]: set argument is not an NSSet
2015-11-11 15:19:39.672 Instore[1305:1533766] Stack Trace: (
  0  CoreFoundation                      0x252ac693 <redacted> + 150
  1  libobjc.A.dylib                    0x36cc6e17 objc_exception_throw + 38
  2  CoreFoundation                      0x251c615d <redacted> + 0
  3  CoreData                            0x24fe54f9 <redacted> + 644
  4  CoreData                            0x24fc8f35 <redacted> + 28
  5  Instore                            0x003b970d -[Location settingForName:] + 168
  6  Instore                            0x003b9a65 -[Location settingValue:] + 48
  7  Instore                            0x003b9bff -[Location terminalMode] + 50
  8  Instore                            0x00429643 -[PrintJobOrder generateEpsonCommandsForOrderTicketEpsonLanguage:error:] + 582
  9  Instore                            0x00427623 -[PrintJobOrder generateEpsonCommandsForEpsonLanguage:error:] + 50
  10  Instore                            0x00243557 __46-[PrintingController printLineJob:completion:]_block_invoke + 898
  11  libdispatch.dylib                  0x013bfcdf _dispatch_call_block_and_release + 10
  12  libdispatch.dylib                  0x013cc02f _dispatch_root_queue_drain + 1790
  13  libdispatch.dylib                  0x013cb92f _dispatch_worker_thread3 + 102
  14  libsystem_pthread.dylib            0x375a5b29 _pthread_wqthread + 1024
  15  libsystem_pthread.dylib            0x375a5718 start_wqthread + 8
)
2015-11-11 15:19:39.674 Instore[1305:1533766] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSMutableSet unionSet:]: set argument is not an NSSet'
*** First throw call stack:
(0x252ac67b 0x36cc6e17 0x251c615d 0x24fe54f9 0x24fc8f35 0x3b970d 0x3b9a65 0x3b9bff 0x429643 0x427623 0x243557 0x13bfcdf 0x13cc02f 0x13cb92f 0x375a5b29 0x375a5718)
libc++abi.dylib: terminating with uncaught exception of type NSException


(lldb) po self.settings
Relationship 'settings' on managed object (0x16d75ba0) <Location: 0x16d75ba0> (entity: Location; id: 0x16e4b750 <x-coredata:/
    account = "0x16ff3a20 <x-coredata://3E3D1931-B877-4DDC-923B-2C094638B0F5/OWNAccount/p1>";
    activated = 1;
    "address_1" = "[redacted]";
    "address_2" = "";
    appliedDiscounts = "<relationship fault: 0x16d6b860 'appliedDiscounts'>";
    city = [redacted];
    country = "[redacted]";
    "created_at" = "2015-11-10 15:25:50 +0000";
    "currency_format" = nil;
    "currency_official_name" = nil;
    "currency_symbol" = nil;
    "current_pay_period_id" = nil;
    ingredients = "<relationship fault: 0x16de5b50 'ingredients'>";
    isSelectingTipRequired = 1;
    isTippingAllowed = 1;
    "is_suitable_for_push" = 0;
    name = [redacted];
    "old_location_group_id" = 0;
    permissions = "<relationship fault: 0x16d69490 'permissions'>";
    phone = [redacted];
    "sales_tax" = 0;
    settings =    (
      "0x16ff5be0 <x-coredata://3E3D1931-B877-4DDC-923B-2C094638B0F5/LocationSetting/p1>"
    );
    state = [redacted];
    status = active;
    "sync_order_column" = 133212;
    "sync_record_last_update" = nil;
    "time_zone" = "Eastern Time (US & Canada)";
    "updated_at" = "2015-11-11 19:25:54 +0000";
    url = "[redacted]";
    uuid = "8c6e9336-02d4-4a31-b22b-bb41d26ca1e6";
    zip = [redacted];
}) with objects {(
    <LocationSetting: 0x16fe4680> (entity: LocationSetting; id: 0x16ff5be0 <x-coredata:/
    location =  <LocationSetting: 0x16fe4680> (entity: LocationSetting; id: 0x16ff5be0 <x-coredata://3E3D1931-B877-4DDC-923B-2C094638B0F5/LocationSetting/p1> ; data: {
    name = terminalMode;
    value = quickService;
})
)}

(lldb) po [self.settings class]
_NSFaultingMutableSet


And here's a stack trace just in case it helps.

Whoops looks like the stack trace screenshot didn't go. Here you are...


Thread 8

  1. __pthread_kill
  2. pthread_kill
  3. abort
  4. abort_message
  5. default_terminal_handler()
  6. _objc_terminate()
  7. std::__terminate(void (*)())
  8. __cxa_throw
  9. obj_exception_throw
  10. -[NSMutableSet unionSet:]
  11. -[_NSFaultingMutableSet countByEnumerating...]
  12. -[Location settingForName:]
  13. -[Location settingForValue:]
  14. -[Location terminalMode]
  15. ... more of my code up to the run loop
Accepted Answer

Have you run your code with concurrency debugging turned on? The stuff at the top of that stack frame make it all look suspiciously like a managed object getting processed in a completion handler (just now, in this release) ending up on a different thread than it started on.


As complicated as the Core Data faulting mechanism is, I think it's an even wager that the value of [self settings] changes between the call site that causes the exception and when you get a chance to run the debugger.


Do you need suggestions for work arounds for the potentially buggy fast enumerator code?


P.S. Bug report numbers, please. If it's a Core Data bug, it needs to be reported.

I did not know about the concurrency debugging flag. This is life changing!


I've already found a few concurrency voilations. I'll report back after i've put the whole app through its paces with this flag turned on.

Yep, you were right. There was a sneaky concurrency voilation causing this problem. Interestingly, the other problem I linked to is still reproducable but only on an iPad running iOS 8.

Good to hear that the concurrency debugging helped sort out the issue. 🙂


It's probably better to reserve comment on the other issue for the other thread. If I come up with anything, I'll post it there. Hmm...

CoreData crashed trying to run a pretty boring for...in loop.
 
 
Q