Application Crashes when accessing attribute of a record #197

Closed
opened 2025-12-29 15:26:30 +01:00 by adam · 4 comments
Owner

Originally created by @ujamshy on GitHub (Feb 5, 2018).

Hi, I need your help with a problem that causes our application to crash.
The crash is happening at customers` devices - we couldn't reproduce it on our tests

Architecture:
The relevant table in the database contains log records.
LogManager contains a DataStack and uses it to create/insert/update/delete log records.
LogObserver uses ListMonitor to get notifications about changes to the table and communicates these changes to a remote server.
It runs in its own serial queue and uses un UnsafeTransaction created from the DataStack to handle managed objects.
When it gets a notification about a new/updated object (didInsertObject/didUpdateObject) it performs the following code:

open func listMonitor(_ monitor: ListMonitor<M>, didInsertObject object: M, toIndexPath indexPath: IndexPath) {

    self.localQueue.async {

        // using its UnsafeTransaction to fetch the object
        guard let existingObject = self.transaction.fetchExisting(object) else { return }
        existingObject.refreshAndMerge()
        // handle the object
        self.handlePendingObject(existingObject)
    }
}

LogObserver's transaction is created in its own queue the first time it is used:

lazy var transaction: UnsafeDataTransaction = {

    let transaction = self.dataStack.beginUnsafe()

    return transaction
}()

When handling an object we try to check if it is deleted by testing if its context is nil:

func handlePendingObject(_ object: M) {
   ...

    // test if object was deleted
    if object.managedObjectContext == nil {

        return
    }
}

Crash:
The crash happens sometimes when a record is deleted from the DataStack while still held by the LogObserver.
When printing the record all its attributes are displayed as nil (although they are not optionals).
When trying to access one of the attributes the application crashes with Exception Type: SIGTRAP.

Questions:
I have several questions:

  1. Do we use correctly the UnsafeTransaction?
  2. Is there a better test for deleted object?
  3. Can you suggest a way to avoid the crash?

Thanks :)

Originally created by @ujamshy on GitHub (Feb 5, 2018). Hi, I need your help with a problem that causes our application to crash. The crash is happening at customers` devices - we couldn't reproduce it on our tests **Architecture:** The relevant table in the database contains log records. **LogManager** contains a DataStack and uses it to create/insert/update/delete log records. **LogObserver** uses ListMonitor to get notifications about changes to the table and communicates these changes to a remote server. It runs in its own serial queue and uses un UnsafeTransaction created from the DataStack to handle managed objects. When it gets a notification about a new/updated object (didInsertObject/didUpdateObject) it performs the following code: ``` open func listMonitor(_ monitor: ListMonitor<M>, didInsertObject object: M, toIndexPath indexPath: IndexPath) { self.localQueue.async { // using its UnsafeTransaction to fetch the object guard let existingObject = self.transaction.fetchExisting(object) else { return } existingObject.refreshAndMerge() // handle the object self.handlePendingObject(existingObject) } } ``` **LogObserver**'s transaction is created in its own queue the first time it is used: ``` lazy var transaction: UnsafeDataTransaction = { let transaction = self.dataStack.beginUnsafe() return transaction }() ``` When handling an object we try to check if it is deleted by testing if its context is nil: ``` func handlePendingObject(_ object: M) { ... // test if object was deleted if object.managedObjectContext == nil { return } } ``` **Crash**: The crash happens sometimes when a record is deleted from the DataStack while still held by the LogObserver. When printing the record all its attributes are displayed as nil (although they are not optionals). When trying to access one of the attributes the application crashes with Exception Type: SIGTRAP. **Questions**: I have several questions: 1. Do we use correctly the UnsafeTransaction? 2. Is there a better test for deleted object? 3. Can you suggest a way to avoid the crash? Thanks :)
adam added the question label 2025-12-29 15:26:30 +01:00
adam closed this issue 2025-12-29 15:26:30 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Feb 8, 2018):

CoreStore's transactions are designed more for updating data than observing for updates. There will be a hiccup with your DataStack-to-Transaction synchronization because of the changes propagating in different queues. My suggestion here would be to try using a ListMonitor created from the UnsafeDataTransaction and observe that directly.

That said, I haven't personally used a ListMonitor created from an UnsafeDataTransaction in my projects (although I know some users who had) so I cannot offer advices against existing pitfalls, if there are any.

If you have the luxury of redesigning your architecture, I strongly recommend you reduce the granularity of your transactions and to use perform(asynchronous:...) and perform(synchronous:) when you can. There is a reason that CoreStore's Async and Sync transactions execute serially: they protect you from synchronization issues like these by not having to worry about changes in parallel transactions.

@JohnEstropia commented on GitHub (Feb 8, 2018): CoreStore's transactions are designed more for updating data than observing for updates. There will be a hiccup with your DataStack-to-Transaction synchronization because of the changes propagating in different queues. My suggestion here would be to try using a `ListMonitor` created from the `UnsafeDataTransaction` and observe that directly. That said, I haven't personally used a `ListMonitor` created from an `UnsafeDataTransaction` in my projects (although I know some users who had) so I cannot offer advices against existing pitfalls, if there are any. If you have the luxury of redesigning your architecture, I strongly recommend you reduce the granularity of your transactions and to use `perform(asynchronous:...)` and `perform(synchronous:)` when you can. There is a reason that CoreStore's Async and Sync transactions execute serially: they protect you from synchronization issues like these by not having to worry about changes in parallel transactions.
Author
Owner

@ujamshy commented on GitHub (Feb 8, 2018):

Let me refine my question. Suppose we have DataStack and UnsafeTransaction derived from the DataStack. Suppose also that an object in the DataStack and was added to the unsafe transaction using featchExisting. If the object is deleted from the DataStack should accessing it in the UnsafeTransaction cause exception?

@ujamshy commented on GitHub (Feb 8, 2018): Let me refine my question. Suppose we have DataStack and UnsafeTransaction derived from the DataStack. Suppose also that an object in the DataStack and was added to the unsafe transaction using featchExisting. If the object is deleted from the DataStack should accessing it in the UnsafeTransaction cause exception?
Author
Owner

@JohnEstropia commented on GitHub (Feb 15, 2018):

@ujamshy That's more of Core Data's behavior than anything, and yes that may trigger an exception.

@JohnEstropia commented on GitHub (Feb 15, 2018): @ujamshy That's more of Core Data's behavior than anything, and yes that may trigger an exception.
Author
Owner

@JohnEstropia commented on GitHub (Apr 14, 2018):

I'm closing this issue as this is Core Data's behavior. Let me know if you find a resolution so we can put a warning somewhere in the README.

@JohnEstropia commented on GitHub (Apr 14, 2018): I'm closing this issue as this is Core Data's behavior. Let me know if you find a resolution so we can put a warning somewhere in the README.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#197