Merge conflict when multiple underlying inputs are sent to NSPersistentContainer using executeBackgroundTask function

I experimented with the new underlying NSPersistentContainer data API and was under the impression that the internal queuing mechanism would prevent concurrent evaluation of write transactions, as detailed in this NSPersistentContainer concurrency stack overflow to persist master data

The way many professionals faced the problem for a long time (even before NSPersistentContainer did it) was to have an operation queue for the write queue so that only one write happens at a time, and have a different context in the main thread just for reading. This way you will never get merge conflicts. (see https://vimeo.com/89370886 for a great explanation of this setting, what the NSPersistentContainer is doing internally now). When you call executeBackgroundTask, the persistentContainer puts this block on an internal sequential queue. This ensures that there are no mergeConflicts.

However, if I insert multiple objects that share the assignment assignment in a narrow loop using performBackgroundTask

for each iteration, I get an error NSMergeConflict

while saving the context:

            let bundlePath = Bundle.main.resourceURL!

        let directoryEnumerator = FileManager.default.enumerator(at: bundlePath, includingPropertiesForKeys: [URLResourceKey.isDirectoryKey, URLResourceKey.nameKey])
        while let url = directoryEnumerator?.nextObject() as? URL {

            if url.pathExtension == "jpeg" {
                let imageData = try! Data(contentsOf: url)

                DataManager.persistentContainer.performBackgroundTask { (context) in

                    //          context.mergePolicy = NSMergePolicy.overwrite

                    let new = Photo(context: context)
                    new.name = url.lastPathComponent
                    new.data = imageData as NSData

                    let corresponding = try! context.existingObject(with: DataManager.rootFolder.objectID) as! Folder
                    new.parent = corresponding

                    try! context.save()
                }
            }

      

I posted a sample project on github to demonstrate the problem: https://github.com/MaximeBoulat/NSPersistentContainer_Merge_Conflict

It seems to be crashing because multiple objects establish their "parent" relationship at the same time for the same parent, resulting in a desynchronization of the parent "child" relationship to concurrent updates.

This happens even if I set the .automaticallyMergesChangesFromParent

inbound context property to true. I can prevent the crash by defining an inbound context merge policy, but this is not an acceptable solution.

Is there a way to configure the NSPersistentContainer to correctly serialize updates sent using the API performBackgroundTask

. Or is there something I am missing that is causing these updates to conflict with each other? Or did Apple provide the NSPsistentContainer with a stack of expectations that any conflicts that arise when evaluating the logic passed to performBackgroundTask

must either be fatal or ignored?

+3


source to share


2 answers


From the documentation on performBackgroundTask(:)

:

Each time this method is called, the persistent container creates a new NSManaged Object Context with the concurrency type set to the private Queue Concurrency Type. The persistent container then executes this newly created context passed in the block on the private context queue



So, I don't think I am doing what you want. I think you want to name it newBackgroundContext()

, store it in some property and use it performBlock(:)

on it whenever you need this serialization.

+1


source


I wrote the answer you are citing. I was wrong. I have updated it.

I found I NSPersistentContainer

performBackgroundTask

don't have a functional inner queue and this can lead to merge conflicts. When I first tested it it seemed like it was, but I found out, like you, that there could be conflicts. Fortunately, it's not that hard to fix this by creating your own queue. I know it seems odd for Apple that it released something that was broken, but it looks like it does.



I am sorry for the published incorrect information.

+1


source







All Articles