4

Note:
This post does not apply since I actually use CoreData.
In this post the last answer suggests to fetch all items in the new background thread before adding new objects, but this is done in my code.
This post suggests to unfault an item before its context is saved, but this is also done in my code.

My app uses CoreData to store objects called shoppingItems. I wrote a class CoreDataManager that initialises CoreData and has essentially one function to overwrite the currently stored items, and one function to fetch all items. Both functions operate in the background, i.e. on a separate thread.

Here is my code (irrelevant parts are left out).

I setup core data on the main thread:

private lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: modelName)
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    })
    return container
}()  

This is the write function:

func overwriteShoppingItems(_ shoppingItems: Set<ShoppingItem>, completion: @escaping (Error?) -> Void) {
        let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        let viewContext = self.persistentContainer.viewContext
        backgroundContext.parent = viewContext
        backgroundContext.performAndWait {
            // Delete currently stored shopping items
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: CDEntityShoppingItem)
            do {
                let result = try backgroundContext.fetch(fetchRequest)
                let resultData = result as! [NSManagedObject]
                for object in resultData {
                    backgroundContext.delete(object)
                }

                if !shoppingItems.isEmpty {
                    // Save shopping items in managed context
                    let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: CDEntityShoppingItem, in: backgroundContext)!
                    for nextShoppingItem in shoppingItems {
                        let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity, insertInto: backgroundContext)
                        nextCdShoppingItem.name = nextShoppingItem.name
                    }
                }

                let saveError = self.saveManagedContext(managedContext: backgroundContext)
                completion(saveError)
            } catch let error as NSError {
                completion(error)
            }
        }
}

func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
    if !managedContext.hasChanges { return nil }
    do {
        try managedContext.save()
        return nil
    } catch let error as NSError {
        return error
    }
}

And this is the fetch function:

func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
        let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        let viewContext = self.persistentContainer.viewContext
        backgroundContext.parent = viewContext
        backgroundContext.performAndWait {
            let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
            do {
                let cdShoppingItems: [CDShoppingItem] = try backgroundContext.fetch(fetchRequest)
                guard !cdShoppingItems.isEmpty else {
                    completion([], nil)
                    return
                }
                for nextCdShoppingItem in cdShoppingItems {
                }
                    completion(shoppingItems, nil) 
            } catch let error as NSError {
                completion(nil, error)
            }
        }
}  

In normal operation, the code seems to work.

The problem:

I also wrote a unit test that tries to provoke multi-threading problems. This test uses a concurrent dispatch queue:

let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)  

A timer defines the test time.
In the scheme for tests, I have set the arguments -com.apple.CoreData.Logging.stderr 1 and -com.apple.CoreData.ConcurrencyDebug 1.
During the test time, overwriteShoppingItems and fetchShoppingItems are inserted repeatedly in the queue, and executed concurrently.
This unit test executes a few reads and writes, before it stops at the line

                    let itemName = nextCdShoppingItem.name!  

because nextCdShoppingItem.name is nil, which should never happen, because I never store nil.

Immediately before the crash, the following is logged:

CoreData: error:  API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)

If I do only fetches, or only writes, the CoreData warning is not logged. Thus it seems definitely to be a multi-threading issue. However, CoreData.ConcurrencyDebug does not detect it.

It looks as if during a fetch operation on one thread, the other thread deletes the currently fetched item, so that its properties are read back as nil. But this should not happen because fetches and saves are done with backgroundContext.performAndWait, i.e. serially. And the stack trace shows that only a single thread accesses CoreData: Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)

My questions:

  • What is my misuse of the CoreData API, and how to do it right?
  • Why is an item sometimes read back as nil?

EDIT:

Maybe this helps to identify the problem: When I comment out backgroundContext.delete(object) in overwriteShoppingItems, the error is no longer logged, and no item is fetched as nil.

5 Answers 5

18

Okay so i got same error but only while "archiving" the project. Took me several hours to find the issue, since I was getting the error only on uploading, in the assets catalog, I had some duplicate images, by removing them the error is gone.

Anyone else getting this error AND your Coredata setup is fine, check your assets folder.

Sign up to request clarification or add additional context in comments.

5 Comments

CompileAssetCatalog failed because I had some "single size" App Icons and some for every size. Moving all to single size helped a lot!
"AppIcon" appiconset and an "AppIcon" imageset in assets, duplicate "AppIcon" name in my assets was causing this error in Xcode 14.2 (14C18). Changing the name of one of them resolved this issue.
@Colin Excellent suggestion, thank you! In my case the error was indeed caused by the app’s alternate icon configured with a single size, but its primary icon with individual sizes. Switching all icons to individual sizes has fixed the issue (taking the opposite approach and using the single size everywhere wasn’t an option for me).
I had the same name for an image file and an audio file. I changed one of them and the error is gone. Thanks a lot!
Besides all helpful suggestions above I also had to set "Include All App Icon Assets" to "No" and add my alternate app icon names in the "Alternate App Icon Sets" value. With "Include All App Icon Assets" on "Yes" I still could not archive.
5

If you are not using core data in your project then check your assets files are proper or not.

I am also getting error like this then i have findout this error from my project.

Comments

3

Problem seems to be solved.
It happened apparently, because the functions overwriteShoppingItems and fetchShoppingItems both setup a separate background context with let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) with their own queue so that fetches and saves were not serialized by a single queue.

I modified my code now in the following way:
I have now a property

let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)  

that is initialized as

self.backgroundContext.persistentStoreCoordinator = self.persistentContainer.persistentStoreCoordinator
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

and fetches and saves are done using

backgroundContext.perform {…}  

Now the CoreData error is no longer logged, and no item is fetched as nil.

Comments

2

In my case I created an new managedObjectContext without (yet) assigning a persistent store. Eventually I called -[psc metadataForPersistentStore:store] with the store being nil. This caused the log message to be written. (side-note: you can always catch these outputs by setting a breakpoint on fprintf)

1 Comment

I wish I could give 100 upvotes for the tip on fprintf. For years, I've tried to break on these unidentified warnings from within the bowels of Apple's frameworks; I've set breakpoints on printf, NSLog, asl_log, os_log, asl_send, and maybe more, but it never occured to me try fprintf. But it turns out that was the missing breakpoint, at least in this case. I've found the problem!
0

If you are seeing this during tear-down of a Core Data stack, check for un-removed observers or memory leaks.

In my case, I had managed objects which were each observing themselves – one of their relationships, using Swift's Key-Value Observing (NSKeyValueObservation objects). (Example use case: When the last a department's employees are deleted, delete the department.) The console messages appeared while the containing Core Data document was being closed. By breaking on fprintf I could see that the message was logged when a notification was being sent to an observer, and the number of messages was always equal to the number of non-faulted such objects in the managed object context (5 departments --> 5 console messages, etc., etc.). The problem was that I was not removing these observations, and the solution was of course to implement func willTurnIntoFault() in the observing objects, and in this func remove the observation.

In another situation, these messages stopped after I fixed a memory leak, but I did not study that in detail.

The full error messages were: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0) CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0) and they occurred during the call to NSPersistentStoreCoordinator.remove(_:). So, this error is telling me that the store's PSC was nil, but I checked this with the debugger and found that the store's PSC was not nil before the call. I filed a bug to Apple on this (FB11669843) asking if they could improve the error message.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.