Search code examples
swiftswiftuicore-datacrashxcode-organizer

container.loadPersistentStores crashes upon PersistenceController initialization


I have this CoreData crash that happens every now and then:

crash from Xcode organizer

For the CoreData implementation, I mainly used the default implementation, with some additions to make it work with my widget (using app groups):

init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "Main")
    
    if let description = container.persistentStoreDescriptions.first {
        description.shouldMigrateStoreAutomatically = true
        description.shouldInferMappingModelAutomatically = true
        
        // This was already added to make sure the widget is in read-only mode
        if !inMemory && Bundle.main.bundleIdentifier != Constants.appBundleID {
            description.isReadOnly = true
        }
    }
    
    if inMemory {
        container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
    } else {
        if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupID),
           let description = container.persistentStoreDescriptions.first {
            description.url = url.appendingPathComponent("Main.sqlite")
        }
    }
    
    container.viewContext.automaticallyMergesChangesFromParent = true
    container.loadPersistentStores { storeDescription, error in // Crash happens here
        // This is not called
        if let error = error {
            assertionFailure("Unresolved error \(error.localizedDescription)")
        }
    }
}

I already found a very similar crash (with similar stack trace) here, but I'm not using any background threads to access the PersistenceController, so the solution doesn't fit my needs. However, I think the widget and iOS app might be trying to initialize the PersistenceController at the same time, thus creating a somewhat similar deadlock scenario.

I also found this and added some code, which would make the created PersistenceController in the widget read-only (see comment). Unfortunately, this didn't solve the crash, as it's still happening.

Does anyone know on how to fix this crash?


Solution

  • After filing a TSI with Apple, I came to the conclusion that it might be CoreData migrations, which cause this crash. Somehow they might take longer than expected and get killed by the system.

    What helped in my case was adding performExpiringActivity around loadPersistentStores.

    Here's how this could look like:

        #if WIDGETS || os(macOS)
        loadPersistentStores(container: container)
        #else
        // Ask for additional time before the app gets suspended.
        let processInfo = ProcessInfo()
        processInfo.performExpiringActivity(withReason: "Load persistent stores") { [self] expired in
            guard !expired else {
                return
            }
            
            self.loadPersistentStores(container: container)
            sleep(2)
        }
        #endif
    }
    
    private func loadPersistentStores(container: NSPersistentContainer) {
        container.loadPersistentStores { storeDescription, error in
            if let error = error {
                assertionFailure("Unresolved error \(error.localizedDescription)")
            }
        }
        
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
    }
    

    performExpiringActivity is not working inside widgets or macOS, so I added a check for that. I added a custom Swift Flag to determine if the code is running inside a widget.

    Since implementing this (around 3-4 weeks ago) I didn't face the crash anymore.