12

I'm using Apple's concurrency core data debugger.

-com.apple.CoreData.ConcurrencyDebug 1

From time to time I got __Multithreading_Violation_AllThatIsLeftToUsIsHonor__, even I'm almost sure threading is not violated.

This is part of code where exception occurs (code is part of protocol that extends NSManagedObject):

public static func find(arrayBy predicate: NSPredicate, sort: [NSSortDescriptor] = [], limit: Int? = nil) -> [Self] {
    let fetchRequest = NSFetchRequest<Self>(entityName: "\(Self.self)")
    fetchRequest.predicate = predicate
    fetchRequest.sortDescriptors = sort

    do {
        return try Context.current.fetch(fetchRequest) // Exception!!!
    } catch let error {
        Logger.fatal("Failed to perform fetch: \(error)")
        return []
    }
}

Code is executed within context's perform: block.

Here is thread information:

enter image description here

and debuger info to confirm that perform is executed on the right NSManagedContext:

(lldb) po Context.current
<StoreContext: 0x7f854b556610>

Entity name is extracted successfully:

po fetchRequest.entityName!
"Position"

Predicate is constructed of pure String objects (no managed objects used at all):

(lldb) po fetchRequest.predicate!
ANY employees.company.id == "282372"

Sort descriptors are not used at all in this case:

po fetchRequest.sortDescriptors!
0 elements

Limit is completely ignored.

What am I missing? Does anyone has any idea what can be wrong here?

Edit:

To clarify, Context.current is set just before dispatching the block:

Context.current = managedObjectContext
managedObjectContext.performAndWait {
   //...
}

You can see on the screenshot that Thread 13 is running on Queue: NSManagedObject 0x7f854b556610 (serial). Also, when exception occurs Context.current returns <StoreContext: 0x7f854b556610>. By looking at the memory address it's easy to conclude block is executing on the right queue.

XeNoN
  • 694
  • 6
  • 16
  • can you try with `com.apple.CoreData.ConcurrencyDebug 1` flag and see if you find anything? – Vishal Singh Feb 02 '17 at 11:06
  • I'm already using it, that is why multithreading violation exception is raised. – XeNoN Feb 02 '17 at 11:11
  • what is `Context.current`? why aren't you use the managedObject own context (`self.managedObjectContext`)? – Jon Rose Feb 05 '17 at 14:13
  • Because I don't have managed object instance. This is class method and objects are instantiated during successful fetch. `Context.current` is the class field with cached `managedObjectContext` during `perform` call. That's not a problem, debug info shows that thread queue and context are same. – XeNoN Feb 05 '17 at 20:33
  • Can you share the code for `Context.current`? How do you know that's returning the correct context for the current thread? – Dave Weston Feb 06 '17 at 22:06
  • Take a look at the post edit. – XeNoN Feb 19 '17 at 01:36
  • I know that the predicate _looks_ like it only has a string NOW as you stated in your question, but did you use a managedObject in building the predicate? Is that managedObject on the correct thread? – Jon Rose Feb 20 '17 at 07:01
  • I'm passing string variable to `NSPredicate(format:)`. `managedObject` is used to initialize variable like `let id = managedObject.id`, but at that point no exception occurred and I check that it is on correct thread. – XeNoN Feb 20 '17 at 10:32
  • What is `StoreContext`? Did you subclass `NSManagedObjectContext`? Documentation strongly discourages subclassing contexts. Try replacing this call with standard CoreData code, spin off new `NSManagedObjectContext` and run `performBlock`. There is no point of returning array of objects when they are outside of context anyway. You should be safe passing `NSManagedObjectIDs` around. – pronebird Feb 25 '17 at 19:45
  • https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/Concurrency.html – James Bush Apr 21 '17 at 22:34
  • @XeNoN what was your final solution? – ricardopereira Oct 16 '19 at 13:46

3 Answers3

3

It is recommended to avoid using the .performAndWait API, to be used only at the rarest of the rare occasions, when everything else has failed!
Evaluate morphing Context.current to managedObjectContext.perform throughout the application.
The effect of this change would be addition of asynchronicity in all your database operations.
It may sound of a massive change to ask for but trust me, just decide to treat Core Data as a completely asynchronous API & life will be much better this way.
I'm sure the current crash you are facing is a result of compound result of the corrupted behavior of .performAndWait

This, this and this are some good reads on the topic.

Community
  • 1
  • 1
ystack
  • 1,785
  • 12
  • 23
3

Storing the "current" background context in a global state is a bad practice. I can't point out where exactly in your code it is messing up, but unexpected things can happen with global state when multithreading is involved. Change your find function to accept a context as a parameter. This will avoid using any global state and is will likely fix your problem.

Jon Rose
  • 8,373
  • 1
  • 30
  • 36
  • I agree, but in this particular case protocol is used to achieve active record pattern (method return value type is `[Self]`) and adding context as parameter make no sense. Debug info shows that problem isn't directly related to execution on the wrong queue. I'm starting to guess there is a problem somewhere else in my code but exception occurs here due invalid core data graph (some objects in existing graph may be from another context). – XeNoN Feb 19 '17 at 11:27
  • Each `NSManagedObject` has a pointer to the `managedObjectContext` that is is associated with. – Jon Rose Feb 19 '17 at 15:28
  • 1
    Your code sets `Context.current` and then calls `performAndWait`, which is fine in isolation. However, it's mutable global state, there is nothing to protect `Context.current` from being changed while the `performAndWait` is working. I'm not that familiar with Ruby, but doesn't it largely single-threaded? If you wanted to maintain the simplicity of your current interface, maybe you can just enforce that everything happens on the main thread. – Dave Weston Feb 20 '17 at 23:43
  • 2
    This is not an answer but a comment. – Léo Natan Feb 24 '17 at 19:58
  • @DaveWeston I think you mistakenly went into Swift thread. It's not Ruby question... – pronebird Feb 25 '17 at 19:43
  • The original poster mentioned active record pattern, so I immediately went to ActiveRecord from Rails. The point is that patterns that work in one framework with particular characteristics might not work in other frameworks when those characteristics change. With pervasive multithreading, you can't just store a value somewhere and then expect to get the same value back somewhere else. – Dave Weston Feb 25 '17 at 19:53
3

Check out this blog to get better understanding of multithreading in Core Data

https://cocoacasts.com/core-data-and-concurrency/

https://cocoacasts.com/more-core-data-and-concurrency/

Yakiv Kovalskyi
  • 1,737
  • 1
  • 15
  • 29