Getting exceptions accessing properties of objects, due to deletions? #70

Closed
opened 2025-12-29 15:23:35 +01:00 by adam · 12 comments
Owner

Originally created by @JoeMatt on GitHub (Aug 24, 2016).

We have a bunch of random CoreData crashes in our user crash logs that we rarely see in our tests. All due to being unable to resolve a fault. Or scheme looks basically like this.

Book.chapters -> [Chapters].pages-> [Pages]

We never delete pages directly, but a chapter will get a new array of of pages and the pages are set to delete in the .xcscheme when they lose their parent.

In another part of the code there might be something that working the old pages they just got from a fetch. I go to access a property of a page that, I'm guessing here, has been cleared by CData. Our network code is all async so we'd be doing this work in another thread.

How are we supposed to be handling this situation? I know CStore is handling threading for us for queries and inserts and such, but once I have an object another thread could at the same time delete it before I finish my work with it. I think this is what's causing our random crashes since it's only happening on object types that sometimes get deleted while the app is running.

Sorry if this is more a core data question and not specific to CoreStore, this is my first large scale CoreData app and the only Google queries related to these crashes suggest looking at threads but like I said, I'm not specifically deleting these objects, it's directly after fetching the array so it might be a very tight race condition but it's effecting about 6% of all sessions with crashes.

Originally created by @JoeMatt on GitHub (Aug 24, 2016). We have a bunch of random CoreData crashes in our user crash logs that we rarely see in our tests. All due to being unable to resolve a fault. Or scheme looks basically like this. Book.chapters -> [Chapters].pages-> [Pages] We never delete pages directly, but a chapter will get a new array of of pages and the pages are set to delete in the .xcscheme when they lose their parent. In another part of the code there might be something that working the old pages they just got from a fetch. I go to access a property of a page that, I'm guessing here, has been cleared by CData. Our network code is all async so we'd be doing this work in another thread. How are we supposed to be handling this situation? I know CStore is handling threading for us for queries and inserts and such, but once I have an object another thread could at the same time delete it before I finish my work with it. I think this is what's causing our random crashes since it's only happening on object types that sometimes get deleted while the app is running. Sorry if this is more a core data question and not specific to CoreStore, this is my first large scale CoreData app and the only Google queries related to these crashes suggest looking at threads but like I said, I'm not specifically deleting these objects, it's directly after fetching the array so it might be a very tight race condition but it's effecting about 6% of all sessions with crashes.
adam added the discussion label 2025-12-29 15:23:35 +01:00
adam closed this issue 2025-12-29 15:23:35 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Aug 24, 2016):

First of all, make sure your relationships are properly set with inverse-relationships.

Other than that, I think the only way to avoid this is to look for ways to protect your "pages" from being accessed after getting deleted (via cascade deletion).

One way we work around this is to just set the Delete Action to "Nullify" and set a flag such as "isRemoved" to indicate that the objects is in a "deleted" state. Doing so, the "deletion" is transparent to CoreData and your objects will not be in an unreliable state. The app can then delete those objects in a non-critical time (e.g. during app launch)

@JohnEstropia commented on GitHub (Aug 24, 2016): First of all, make sure your relationships are properly set with inverse-relationships. Other than that, I think the only way to avoid this is to look for ways to protect your "pages" from being accessed after getting deleted (via cascade deletion). One way we work around this is to just set the Delete Action to "Nullify" and set a flag such as "isRemoved" to indicate that the objects is in a "deleted" state. Doing so, the "deletion" is transparent to CoreData and your objects will not be in an unreliable state. The app can then delete those objects in a non-critical time (e.g. during app launch)
Author
Owner

@JoeMatt commented on GitHub (Aug 24, 2016):

This is really helpful, I'll give that a go, thanks.

@JoeMatt commented on GitHub (Aug 24, 2016): This is really helpful, I'll give that a go, thanks.
Author
Owner

@JohnEstropia commented on GitHub (Aug 24, 2016):

Just to make sure though, do you have any stack trace we can check? Or some code to give an idea of how your transactions are set up? Since CoreStore implements all updates in a serial transaction, there should not be a case where multiple contexts (threads) access deleted objects from other contexts (or at least not explicitly). It's either we're hitting a Core Data bug, or your objects are being retained somewhere.

@JohnEstropia commented on GitHub (Aug 24, 2016): Just to make sure though, do you have any stack trace we can check? Or some code to give an idea of how your transactions are set up? Since CoreStore implements all updates in a serial transaction, there should not be a case where multiple contexts (threads) access deleted objects from other contexts (or at least not explicitly). It's either we're hitting a Core Data bug, or your objects are being retained somewhere.
Author
Owner

@JoeMatt commented on GitHub (Aug 26, 2016):

I'm leaving for vacation for 2 weeks so I can't get a good code sample. I made some tweaks in a few parts and we're going to see if our crash numbers improve.

For some reason we have a lot of users crashing on DB init also. I copied the code right out of the sample so not sure what that's about either.

internal let cachesDirectory             = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!
internal let dbDirectory                 = cachesDirectory.URLByAppendingPathComponent("CoreStore", isDirectory: true)

    private static var sqlURL : NSURL = {
        let fileName = "Cache.sqlite"
        let fileURL = dbDirectory.URLByAppendingPathComponent(fileName, isDirectory: false)
        return fileURL
    }()

    static let cacheStack: DataStack = {

        let migrationChain = MigrationChain(arrayLiteral: "DataManager", "DataManager 2", "DataManager 3", "DataManager 4", "DataManager 5", "DataManager 6")
        let bundle         = NSBundle(forClass: CoreDataDataManagerAccessor.self)
        let dataStack      = DataStack(modelName: "DataManager", bundle: bundle, migrationChain:migrationChain)

        do {
            try dataStack.addSQLiteStoreAndWait(
                fileURL: sqlURL,
                configuration: "Cache",
                resetStoreOnModelMismatch: true) // Disable for production
        } catch {
            ELOG("Couldn't make SQL store:  \(error as NSError)")
            fatalError("all  ur base") // <<<===== This is where we crash. Can't recover really.
        }

        // Create cache directory if doesn't exist
        let fm = NSFileManager.defaultManager()
        let cachePath = CoreDataAssetCache.cacheDirectory
        if fm.fileExistsAtPath(cachePath.path!) == false {
            do {
                try fm.createDirectoryAtURL(cachePath, withIntermediateDirectories: true, attributes: nil)
            } catch {
                ELOG("Error create asset storage directory: \(error as NSError)")
            }
        }

        return dataStack
    }()

This is the corestore error,

| 21:11:25:051 | /Users/.../CoreDataCache.swift.CoreDataAssetCache:46 Couldn't make SQL store: Error Domain=com.corestore.error Code=4 "(null)"

What's weird is that this code hasn't changed for many releases except adding new versions to the migration chain. But in our testing of migration between versions we haven't seen crashes. I would also expect it to just dump the old DB and create a new one if that was the issue. We have been updating CoreStore versions with each release though to match the newest, so I wonder if something in CS was updated that would be crashing here?

I switched to the new v2 init methods for our next release, probably won't fix it but maybe?

@JoeMatt commented on GitHub (Aug 26, 2016): I'm leaving for vacation for 2 weeks so I can't get a good code sample. I made some tweaks in a few parts and we're going to see if our crash numbers improve. For some reason we have a lot of users crashing on DB init also. I copied the code right out of the sample so not sure what that's about either. ``` internal let cachesDirectory = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first! internal let dbDirectory = cachesDirectory.URLByAppendingPathComponent("CoreStore", isDirectory: true) private static var sqlURL : NSURL = { let fileName = "Cache.sqlite" let fileURL = dbDirectory.URLByAppendingPathComponent(fileName, isDirectory: false) return fileURL }() static let cacheStack: DataStack = { let migrationChain = MigrationChain(arrayLiteral: "DataManager", "DataManager 2", "DataManager 3", "DataManager 4", "DataManager 5", "DataManager 6") let bundle = NSBundle(forClass: CoreDataDataManagerAccessor.self) let dataStack = DataStack(modelName: "DataManager", bundle: bundle, migrationChain:migrationChain) do { try dataStack.addSQLiteStoreAndWait( fileURL: sqlURL, configuration: "Cache", resetStoreOnModelMismatch: true) // Disable for production } catch { ELOG("Couldn't make SQL store: \(error as NSError)") fatalError("all ur base") // <<<===== This is where we crash. Can't recover really. } // Create cache directory if doesn't exist let fm = NSFileManager.defaultManager() let cachePath = CoreDataAssetCache.cacheDirectory if fm.fileExistsAtPath(cachePath.path!) == false { do { try fm.createDirectoryAtURL(cachePath, withIntermediateDirectories: true, attributes: nil) } catch { ELOG("Error create asset storage directory: \(error as NSError)") } } return dataStack }() ``` This is the corestore error, > | 21:11:25:051 | /Users/.../CoreDataCache.swift.CoreDataAssetCache:46 Couldn't make SQL store: Error Domain=com.corestore.error Code=4 "(null)" What's weird is that this code hasn't changed for many releases except adding new versions to the migration chain. But in our testing of migration between versions we haven't seen crashes. I would also expect it to just dump the old DB and create a new one if that was the issue. We have been updating CoreStore versions with each release though to match the newest, so I wonder if something in CS was updated that would be crashing here? I switched to the new v2 init methods for our next release, probably won't fix it but maybe?
Author
Owner

@JohnEstropia commented on GitHub (Aug 26, 2016):

I think the problem is you have migrate-able models specified with MigrationChain, but you are using the synchronous addSQLiteStoreAndWait() method which is documented to not support migrations.

For stores that expect migrations, you should use the asynchronous addSQLiteStore(..., completion: ...) method instead.

@JohnEstropia commented on GitHub (Aug 26, 2016): I think the problem is you have migrate-able models specified with `MigrationChain`, but you are using the synchronous `addSQLiteStoreAndWait()` method which is [documented to not support migrations](https://github.com/JohnEstropia/CoreStore/tree/1.6.10#migrations). For stores that expect migrations, you should use the asynchronous `addSQLiteStore(..., completion: ...)` method instead.
Author
Owner

@JohnEstropia commented on GitHub (Aug 26, 2016):

If you prefer to just dump old models and recreate new ones, try not not pass a MigrationChain to the DataStack at all.

@JohnEstropia commented on GitHub (Aug 26, 2016): If you prefer to just dump old models and recreate new ones, try not not pass a `MigrationChain` to the `DataStack` at all.
Author
Owner

@JoeMatt commented on GitHub (Aug 26, 2016):

Oops. ha, thanks.

If we're waiting for the store to complete migrating and the completion handler for addSQLiteStore() hasn't fired yet, what happens if queries are sent to the stack? Does it queue them or will it crash or they just disappear.

I ask since we assume with the way we do init now, since static initlizizers are in a dispatch_once(), that we can safely not worry about racing so we don't currently have a linear boot up process. We just fire off jobs and whomever hits it first we don't worry.

@JoeMatt commented on GitHub (Aug 26, 2016): Oops. ha, thanks. If we're waiting for the store to complete migrating and the completion handler for addSQLiteStore() hasn't fired yet, what happens if queries are sent to the stack? Does it queue them or will it crash or they just disappear. I ask since we assume with the way we do init now, since static initlizizers are in a dispatch_once(), that we can safely not worry about racing so we don't currently have a linear boot up process. We just fire off jobs and whomever hits it first we don't worry.
Author
Owner

@JohnEstropia commented on GitHub (Aug 26, 2016):

If we're waiting for the store to complete migrating and the completion handler for addSQLiteStore() hasn't fired yet, what happens if queries are sent to the stack? Does it queue them or will it crash or they just disappear.

Right now CoreStore doesn't support asynchronous fetching (sounds like a good feature though), so queries will just return empty values (nil or []) if no stores exist. but it should not crash as long as the entity you are querying is declared in the DataStack' s model.

@JohnEstropia commented on GitHub (Aug 26, 2016): > If we're waiting for the store to complete migrating and the completion handler for addSQLiteStore() hasn't fired yet, what happens if queries are sent to the stack? Does it queue them or will it crash or they just disappear. Right now CoreStore doesn't support asynchronous fetching (sounds like a good feature though), so queries will just return empty values (`nil` or `[]`) if no stores exist. but it should not crash as long as the entity you are querying is declared in the `DataStack`' s model.
Author
Owner

@JohnEstropia commented on GitHub (Aug 26, 2016):

Because I like the idea of asynchronous, lazy fetches, I added a feature request here: https://github.com/JohnEstropia/CoreStore/issues/88

@JohnEstropia commented on GitHub (Aug 26, 2016): Because I like the idea of asynchronous, lazy fetches, I added a feature request here: https://github.com/JohnEstropia/CoreStore/issues/88
Author
Owner

@JoeMatt commented on GitHub (Aug 26, 2016):

Cool, synchronizing boot up of databases is always tricky, we have a few different stores actually plus some other data processing that happens at startup. We'll realistically have to write code to coordinate the start of these stores with a loading screen but #88 would be a good fallback just in case.

@JoeMatt commented on GitHub (Aug 26, 2016): Cool, synchronizing boot up of databases is always tricky, we have a few different stores actually plus some other data processing that happens at startup. We'll realistically have to write code to coordinate the start of these stores with a loading screen but #88 would be a good fallback just in case.
Author
Owner

@JohnEstropia commented on GitHub (Aug 26, 2016):

I also recommend using ListMonitors where you can. They handle store changes well so your observers are notified properly when migration completes.

@JohnEstropia commented on GitHub (Aug 26, 2016): I also recommend using `ListMonitor`s where you can. They handle store changes well so your observers are notified properly when migration completes.
Author
Owner

@JohnEstropia commented on GitHub (Aug 29, 2016):

I'm closing this issue for now, but if you have other concerns feel free to continue the discussion :)

@JohnEstropia commented on GitHub (Aug 29, 2016): I'm closing this issue for now, but if you have other concerns feel free to continue the discussion :)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#70