18

I'm using the

-com.apple.CoreData.ConcurrencyDebug

argument on launch to debug concurrency in my CoreData app.

During app launch, I perform an asynchronous fetch on the main thread's managed object context.

// set up the async request
   NSError * error = nil;
        [MOC executeRequest:asyncFetch error:&error];
        if (error) {
            NSLog(@"Unable to execute fetch request.");
            NSLog(@"%@, %@", error, error.localizedDescription);
        }

This code is called from the main thread, but executeRequest: enqueues it to another thread, which I understand to be the correct behavior.

The concurrency debugger doesn't like this, saying (I reckon) that I'm doing something wrong here. I've also tried wrapping this in [MOC performBlock:] which also works, but also causes a multithreading violation. In both cases I get this :

[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__

Am I using async fetches incorrectly, or is the concurrency debugger wrong here?

EDIT : I've also tried wrapping it in MOC performBlock which should ensure that it gets called from the main thread. In any case, the call is enqueued from the main thread, but executed elsewhere.

It says the fetch has been enqueued from the main thread, but it seems to actually happen on another.

EDIT : here's the fetch request:

 NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"MyEntity"];

 NSPredicate * pred = [NSPredicate predicateWithFormat:@"boolProperty == YES"];
    fetchRequest.predicate = pred;
 NSSortDescriptor * sort = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
 fetchRequest.sortDescriptors = @[sort];      fetchRequest.propertiesToFetch = @[@"prop1", @"prop2", @"prop3", @"prop4"];

 NSPersistentStoreAsynchronousFetchResultCompletionBlock resultBlock = ^(NSAsynchronousFetchResult *result) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:kFetchCompleteNotification object:result];
        });
    };

 NSAsynchronousFetchRequest *asyncFetch = [[NSAsynchronousFetchRequest alloc]
                                              initWithFetchRequest:fetchRequest
                                              completionBlock:resultBlock];

Then I receive the results from the notification:

- (void)fetchCompletedNote:(NSNotification *)note {
    NSAsynchronousFetchResult * result = note.object;
    if (![cachedResults isEqualToArray:result.finalResult]){
        cacheResults = result.finalResult;
        [self.collectionView reloadData];
    }
}
olynoise
  • 2,016
  • 2
  • 19
  • 32

2 Answers2

11

I think this is Apple's bug.

I filed bug report: https://openradar.appspot.com/30692722

and added example project that reproduces issue: https://github.com/jcavar/examples/tree/master/TestAsyncFetchCoreData

Also, if you don't want to disable flag just because of this issue you may want to swizzle __Multithreading_Violation_AllThatIsLeftToUsIsHonor__ method on NSManagedObjectContext just for this part of code. You need to revert that after request is executed so you get violations for real issues.

josip04
  • 224
  • 3
  • 13
  • 1
    Fantastic. Core Data is so confusing. I think I will use Realm in my next project. – Tamás Sengel Jul 03 '17 at 07:52
  • 1
    Thanks. did you ever get an acknowledgement about this Radar? Seems like it's still happening even in iOS11 Beta2. – Z S Jul 03 '17 at 19:50
  • @ZS no, nothing – josip04 Jul 04 '17 at 08:43
  • 4
    I am pulling my hair out on this one. Stepping thru my code shows that I am on the main thread, yet the concurrency debug shows CoreData`+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__]: – SleepNot Jul 13 '17 at 13:53
7

The concurrency debugger is telling you that you are accessing MOC from the wrong thread/queue. You can only call -executeRequest: error: on the thread/queue that the context belongs to. If this is a NSMainQueueConcurrencyType then you need to be on the main thread. Otherwise, if it is a NSPrivateQueueConcurrencyType you need to use either -performBlock: or -performBlockAndWait: to run the execute on the correct queue.

I added a screenshot, see above. The request is enqueued from the main thread, but executed on another.

Ok, a couple of things:

  1. Is that the line that is breaking/crashing or are you seeing the error output?
  2. Your error handling is incorrect. You should be looking at the result of the -executeRequest: error: and if the result is nil then you are in an error state. The error variable can be populated even with a success.

I note that the code you posted in your screenshot is different than the code you posted previously. Did you add the -performBlock: or just not include it originally?

Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182
  • I am on the main thread when I call [MOC executeRequest]. And it is a NSMainQueueConcurrencyType moc. – olynoise Jul 31 '15 at 00:50
  • You think you are but if you are getting that error then you are not. Put a breakpoint just before that line and see what the debugger tells you. If you are not on zero then you are not on the main thread. – Marcus S. Zarra Jul 31 '15 at 05:09
  • I added a screenshot, see above. The request is enqueued from the main thread, but executed on another. – olynoise Jul 31 '15 at 15:35
  • Updated answer for you. – Marcus S. Zarra Jul 31 '15 at 16:56
  • OK thanks. I've tried both with the fetch wrapped in performBlock: and without with the same result. If I stop the debugger right before the fetch, we are in the main thread. When the exception is thrown, we have been moved to another thread, which (I think) the MOC has spun off to perform the fetch. I'm not holding on to the result of the fetch, because an async fetch has a completion block that returns the results, and that's when I use them. But that does make sense. – olynoise Jul 31 '15 at 17:13
  • What does the `NSFetchRequest` look like? – Marcus S. Zarra Aug 01 '15 at 01:56
  • Getting closer. What about the code on the other end of that notification being broadcast? What is receiving it and what is being done with it? It is starting to look like the results are being touched on another thread instead of the context itself. – Marcus S. Zarra Aug 04 '15 at 13:47
  • Ok, another update -- I think I see how this might be a problem.. though I'm not sure if I totally get it. – olynoise Aug 04 '15 at 16:59
  • That looks fine you are not bouncing between threads. What is the exact text of the error? – Marcus S. Zarra Aug 04 '15 at 17:34
  • 1
    I ended up going a different route -- so at this point I can't recreate the error anymore. (Close to app launch!) But thank you for helping... I actually just got your book in the mail and had a little laugh when I saw your name here :) – olynoise Aug 05 '15 at 17:24
  • 1
    Did you ever solve this properly? Just playing with this API for the first time, and can't for the life of me find a way to use this without it throwing an assertion. The fact that it dispatches internally to the main queue would seem to suggest that it's only designed to be used with `MainQueueConcurrencyType`, but http://devstreaming.apple.com/videos/wwdc/2014/225xxgzhqylosff/225/225_whats_new_in_core_data.pdf suggests that private queue contexts (my current example) can use it also. I'm pretty well experience with CoreData, so not making the "usual" errors, such as not using `performBlock` – Jonathan Crooke Mar 04 '16 at 23:16
  • @itsthejb I doubt that he did, most likely left it in a different design. What issue are you having and why are you using async fetches? They are pretty useless IMHO. Would love to see a test case. – Marcus S. Zarra Mar 05 '16 at 04:13
  • 1
    Just an experiment to see if they would be useful. With this issue, we will do without. – Jonathan Crooke Mar 05 '16 at 12:38
  • 5
    This seems a bug of the concurrency debug option, or perhaps it wasn't updated to account for the async fetch requests. In my own tests, the trap happens on a thread with a call stack of Core Data internal methods. If I remove the debug flag, the completion block would be called on the main thread as expected. – Anders Apr 29 '16 at 08:15
  • The debug option is only a logic bomb. It doesn't alter the flow of the code in the framework. Not sure what you are referring to. Perhaps you should open your own question? – Marcus S. Zarra Apr 29 '16 at 15:47
  • ```performBlock: ``` and ```performBlockAndWait:``` for context that making fetch request are not fixing this issue. – Shmidt Jun 08 '16 at 23:29
  • @Shmidt What? You are not the OP so I do not understand your comment. Perhaps start your own question? – Marcus S. Zarra Jun 09 '16 at 00:19
  • @MarcusS.Zarra I just wanted to say that your answer is not fixing an issue that OP has. – Shmidt Jun 09 '16 at 00:21
  • @Shmidt The OP stopped responding on this question MONTHS ago, why are you resurrecting it? – Marcus S. Zarra Jun 09 '16 at 00:43