Crash When Accessing Required Value #339

Open
opened 2025-12-29 15:29:24 +01:00 by adam · 13 comments
Owner

Originally created by @dylanmansur on GitHub (Aug 18, 2020).

I'm experiencing a crash when accessing a required string property from an entity. I haven't seen anything posted about it, so I'm guessing it could be an implementation issue but I'd appreciate some help finding the right direction to look.

Library version: 6.3.2 (We'll be moving to 7.2.0 in one of our next releases).

Relevant Stack trace:

closure #1 (CoreStore.CoreStoreManagedObject) -> A1 in CoreStore.ValueContainer.Required.value.getter : A1 Value.swift:155
partial apply forwarder for reabstraction thunk helper <A><A1 where A: CoreStore.CoreStoreObject, A1: CoreStore.ImportableAttributeType> from @callee_guaranteed (@guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1?, @error @owned Swift.Error) to @escaping @callee_guaranteed (@in_guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1?, @error @owned Swift.Error) <compiler-generated>:0
partial apply forwarder for reabstraction thunk helper <A><A1 where A: CoreStore.CoreStoreObject, A1: CoreStore.ImportableAttributeType> from @callee_guaranteed (@guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1, @error @owned Swift.Error) to @escaping @callee_guaranteed (@in_guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1, @error @owned Swift.Error)
libswiftCore.dylib
Swift.withExtendedLifetime<A, B>(A, (A) throws -> B) throws -> B
CoreStore.ValueContainer.Required.value.getter : A1 Value.swift:144

We're using a ListMonitor to populate a CollectionView.

It crashes force unwrapping the value in Value.swift ln. 155:

object.value(forKey: self.keyPath)! as! V.QueryableNativeType

The property in question is declared like so:

let token = Value.Required<String>("token", initial: "")

It's not something I've been able to reproduce, but we've seen a decent frequency of it happening. It doesn't seem to happen consistently for users, though (only around 1.5x per affected user).

My assumption would be that if the property was somehow missing, it would fail to insert or update the object.

Again, I'm fully expecting our implementation to be causing this, but if there's any areas you'd recommend investigating it would be much appreciated.

Originally created by @dylanmansur on GitHub (Aug 18, 2020). I'm experiencing a crash when accessing a required string property from an entity. I haven't seen anything posted about it, so I'm guessing it could be an implementation issue but I'd appreciate some help finding the right direction to look. Library version: 6.3.2 (We'll be moving to 7.2.0 in one of our next releases). Relevant Stack trace: ``` closure #1 (CoreStore.CoreStoreManagedObject) -> A1 in CoreStore.ValueContainer.Required.value.getter : A1 Value.swift:155 partial apply forwarder for reabstraction thunk helper <A><A1 where A: CoreStore.CoreStoreObject, A1: CoreStore.ImportableAttributeType> from @callee_guaranteed (@guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1?, @error @owned Swift.Error) to @escaping @callee_guaranteed (@in_guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1?, @error @owned Swift.Error) <compiler-generated>:0 partial apply forwarder for reabstraction thunk helper <A><A1 where A: CoreStore.CoreStoreObject, A1: CoreStore.ImportableAttributeType> from @callee_guaranteed (@guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1, @error @owned Swift.Error) to @escaping @callee_guaranteed (@in_guaranteed CoreStore.CoreStoreManagedObject) -> (@out A1, @error @owned Swift.Error) libswiftCore.dylib Swift.withExtendedLifetime<A, B>(A, (A) throws -> B) throws -> B CoreStore.ValueContainer.Required.value.getter : A1 Value.swift:144 ``` We're using a `ListMonitor` to populate a CollectionView. It crashes force unwrapping the value in `Value.swift` ln. 155: ``` object.value(forKey: self.keyPath)! as! V.QueryableNativeType ``` The property in question is declared like so: ``` let token = Value.Required<String>("token", initial: "") ``` It's not something I've been able to reproduce, but we've seen a decent frequency of it happening. It doesn't seem to happen consistently for users, though (only around 1.5x per affected user). My assumption would be that if the property was somehow missing, it would fail to insert or update the object. Again, I'm fully expecting our implementation to be causing this, but if there's any areas you'd recommend investigating it would be much appreciated.
adam added the question label 2025-12-29 15:29:24 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Aug 19, 2020):

Without any other info my guesses would be

  1. a memory leak
  2. accessing an object already deleted from the store

Can you share some snippets how the token property is being used?

@JohnEstropia commented on GitHub (Aug 19, 2020): Without any other info my guesses would be 1. a memory leak 2. accessing an object already deleted from the store Can you share some snippets how the `token` property is being used?
Author
Owner

@dylanmansur commented on GitHub (Aug 19, 2020):

Sure -
The class it is implemented in is more or less a UICollectionViewDatasource wrapper for a ListMonitor.

In that class, we set up the ListMonitor:

  let recentResourceMonitor = CoreStore.monitorList(
    From<RecentResourceEntity>()
      .orderBy(.descending(\.lastOpenedAt))
  )

And add ourself as an observer, conforming to ListObjectObserver and using the diffing methods to keep track of where we need to animate the UICollectionView updates. (I can post some of this, but there's nothing jumping out to me about it).

The RecentResourceEntity object has an optional relationship -- topic --- that has the property in question on it. The relationship is declared like so:

let topic = Relationship.ToOne<TopicEntity>("topic", inverse: { $0.recentResource }, deleteRule: .cascade)

The crash is happening when we're dequeueing a UICollectionViewCell in collectionView(_:cellForItemAt:). In that method, we're making struct versions of the models (the .detach() method returns the struct copy) to render the cells. It does this for any of its relationships if it finds an object on them:

let recentResourceEntity = self.recentResourceMonitor[indexPath.row]
let recentResource = recentResourceEntity.detach()

It's in the detach() method that the crash seems to always happen.

.

A memory leak seems like a possibility, but I'm unsure why it would surface so consistently in this spot. It's worth noting that the specific property that it happens on is not the first required property that it copies over in that class. I guess I was assuming that an object deletion would trigger on the first required property, but I'm not familiar enough with the codebase to rule it out.

It's on the first screen you see in the app, and seems to happen within a few seconds of the app launching. We have some migration logic happening on launch, but that property has always been required so I'm not sure if that is relevant.

@dylanmansur commented on GitHub (Aug 19, 2020): Sure - The class it is implemented in is more or less a `UICollectionViewDatasource` wrapper for a `ListMonitor`. In that class, we set up the `ListMonitor`: ``` let recentResourceMonitor = CoreStore.monitorList( From<RecentResourceEntity>() .orderBy(.descending(\.lastOpenedAt)) ) ``` And add ourself as an observer, conforming to `ListObjectObserver` and using the diffing methods to keep track of where we need to animate the `UICollectionView` updates. (I can post some of this, but there's nothing jumping out to me about it). The `RecentResourceEntity` object has an optional relationship -- `topic` --- that has the property in question on it. The relationship is declared like so: ``` let topic = Relationship.ToOne<TopicEntity>("topic", inverse: { $0.recentResource }, deleteRule: .cascade) ``` The crash is happening when we're dequeueing a `UICollectionViewCell` in `collectionView(_:cellForItemAt:)`. In that method, we're making `struct` versions of the models (the `.detach()` method returns the `struct` copy) to render the cells. It does this for any of its relationships if it finds an object on them: ``` let recentResourceEntity = self.recentResourceMonitor[indexPath.row] let recentResource = recentResourceEntity.detach() ``` It's in the `detach()` method that the crash seems to always happen. . A memory leak seems like a possibility, but I'm unsure why it would surface so consistently in this spot. It's worth noting that the specific property that it happens on is not the first required property that it copies over in that class. I guess I was assuming that an object deletion would trigger on the first required property, but I'm not familiar enough with the codebase to rule it out. It's on the first screen you see in the app, and seems to happen within a few seconds of the app launching. We have some migration logic happening on launch, but that property has always been required so I'm not sure if that is relevant.
Author
Owner

@JohnEstropia commented on GitHub (Aug 19, 2020):

Do you load your ListMonitor before or after the migration?

@JohnEstropia commented on GitHub (Aug 19, 2020): Do you load your `ListMonitor` before or after the migration?
Author
Owner

@dylanmansur commented on GitHub (Aug 19, 2020):

It looks like we wait to navigate to the relevant screen until we get a result with a success when creating the dataStack.

@dylanmansur commented on GitHub (Aug 19, 2020): It looks like we wait to navigate to the relevant screen until we get a result with a success when creating the `dataStack`.
Author
Owner

@JohnEstropia commented on GitHub (Aug 19, 2020):

May I see the detach() implementation here?

let recentResource = recentResourceEntity.detach()
@JohnEstropia commented on GitHub (Aug 19, 2020): May I see the `detach()` implementation here? ```swift let recentResource = recentResourceEntity.detach() ```
Author
Owner

@dylanmansur commented on GitHub (Aug 19, 2020):

Sure -

  func detach() -> RecentResource {
    return RecentResource(recentResourceEntity: self)
  }

The init is just this. All the omitted lines are just copying over more properties:

struct RecentResource {
  init(recentResourceEntity: RecentResourceEntity) {
    self.id = recentResourceEntity.id.value
    self.type = recentResourceEntity.type.value
    ...
    self.topic = recentResourceEntity.topic.value?.detach()
    ...
  }
}

Same thing with the topic:

  func detach() -> Topic {
    return Topic(topicEntity: self)
  }

Again, all omitted values are just copying things over from the stored model. Mostly strings, ints, and bools.

struct Topic {
  init(topicEntity: TopicEntity) {
    self.id = topicEntity.id.value
    self.updatedAt = topicEntity.updatedAt.value
    ...    
    self.token = topicEntity.token.value
    ...
  }
}

We're not holding on to a reference to the source model or anything as far as I can tell.

@dylanmansur commented on GitHub (Aug 19, 2020): Sure - ``` func detach() -> RecentResource { return RecentResource(recentResourceEntity: self) } ``` The init is just this. All the omitted lines are just copying over more properties: ``` struct RecentResource { init(recentResourceEntity: RecentResourceEntity) { self.id = recentResourceEntity.id.value self.type = recentResourceEntity.type.value ... self.topic = recentResourceEntity.topic.value?.detach() ... } } ``` Same thing with the topic: ``` func detach() -> Topic { return Topic(topicEntity: self) } ``` Again, all omitted values are just copying things over from the stored model. Mostly strings, ints, and bools. ``` struct Topic { init(topicEntity: TopicEntity) { self.id = topicEntity.id.value self.updatedAt = topicEntity.updatedAt.value ... self.token = topicEntity.token.value ... } } ``` We're not holding on to a reference to the source model or anything as far as I can tell.
Author
Owner

@JohnEstropia commented on GitHub (Aug 19, 2020):

There's nothing that stands out here. My guess still is that one of the objects being returned in the relationships are already deleted.

It's worth noting that the specific property that it happens on is not the first required property that it copies over in that class.
I'm not sure but it's possible that the other properties are just left cached somehow.

By the way, you posted the stack trace above but what was the final error message?

@JohnEstropia commented on GitHub (Aug 19, 2020): There's nothing that stands out here. My guess still is that one of the objects being returned in the relationships are already deleted. > It's worth noting that the specific property that it happens on is not the first required property that it copies over in that class. I'm not sure but it's possible that the other properties are just left cached somehow. By the way, you posted the stack trace above but what was the final error message?
Author
Owner

@dylanmansur commented on GitHub (Aug 19, 2020):

Nothing is listed in the Organizer, but AppCenter is listing a SIGTRAP.

@dylanmansur commented on GitHub (Aug 19, 2020): Nothing is listed in the Organizer, but AppCenter is listing a `SIGTRAP`.
Author
Owner

@jimbengtsson92 commented on GitHub (Feb 16, 2023):

I have the exact same problem. This has started to happen when not using asynchronous-transactions when doing fetches at the same time as a different thread is deleting data. We can trigger this error in a unit test.

Should we be performing all fetches on a transaction instead of doing a fetch on the data store? If we do this the crash does not happen, but then we are not able to read data at the same time as we write, so the performance is not as good. Even if we apply a tweak-clause that sets $0.includesPendingChanges = false to the fetch this crash happens.

Please let me know if you need more information.

@jimbengtsson92 commented on GitHub (Feb 16, 2023): I have the exact same problem. This has started to happen when not using [asynchronous-transactions](https://github.com/JohnEstropia/CoreStore#asynchronous-transactions) when doing fetches at the same time as a different thread is deleting data. We can trigger this error in a unit test. Should we be performing all fetches on a transaction instead of doing a fetch on the data store? If we do this the crash does not happen, but then we are not able to read data at the same time as we write, so the performance is not as good. Even if we apply a [tweak-clause](https://github.com/JohnEstropia/CoreStore#tweak-clause) that sets `$0.includesPendingChanges = false` to the fetch this crash happens. Please let me know if you need more information.
Author
Owner

@JohnEstropia commented on GitHub (Feb 16, 2023):

@jimbengtsson92 The best way to hold references to objects that have the possibility of getting deleted in the background is through the ObjectPublisher wrapper instead of the CoreStoreObject subclasses directly. I recommend you work with the CoreStoreObject instances directly only during transactions. At the very least, if you're passing them around different threads, make sure to fetch them from that thread before accessing any of their properties:

let object: MyObject = try dataStack.fetchOne(....)
// elsewhere
someQueue.async {
    withExtendedLifetime(dataStatck.beginUnsafe()) { transaction in
       guard  let object: ObjectSnapshot<MyObject> = object.asSnapshot(in: transaction) else {
           return // object was deleted somewhere else
       }
       // snapshots are hard copies, so it's safe to access fields even if the object is deleted after this point
       print(object.$someField1) // always safe
       print(object.$someField2) // always safe
    }
}

Take note that CoreStoreObjects implement the ObjectRepresentation protocol, which gives you access to different wrappers depending on how you need to use the instances:

  • object.asPublisher(): returns an ObjectPublisher<T>, which is the recommended type for passing around a live instance. You can observe this instance for live changes.
  • object.asSnapshot(): returns an ObjectSnapshot<T>, which is a hard-copy of all field values (except relationships, which still need care when working with multithreads)

When you need to convert from one type to another between threads, these are also available:

  • object.asPublisher(in: dataStack): if somehow you have a background-thread instance you want to create an observable type from the main thread
  • object.asReadOnly(in dataStack): if somehow you have a background-thread instance you want to get a read-only instance from `DataStack's thread (i.e. the main thread)
  • object.asEditable(in: transaction): if you have an object you'd want to edit inside a transaction
  • object.asSnapshot(in: dataStack) and object.asSnapshot(in: transaction): create snapshots of an object coming from another thread
@JohnEstropia commented on GitHub (Feb 16, 2023): @jimbengtsson92 The best way to hold references to objects that have the possibility of getting deleted in the background is through the `ObjectPublisher` wrapper instead of the `CoreStoreObject` subclasses directly. I recommend you work with the `CoreStoreObject` instances directly only during transactions. At the very least, if you're passing them around different threads, make sure to fetch them from that thread before accessing any of their properties: ```swift let object: MyObject = try dataStack.fetchOne(....) // elsewhere someQueue.async { withExtendedLifetime(dataStatck.beginUnsafe()) { transaction in guard let object: ObjectSnapshot<MyObject> = object.asSnapshot(in: transaction) else { return // object was deleted somewhere else } // snapshots are hard copies, so it's safe to access fields even if the object is deleted after this point print(object.$someField1) // always safe print(object.$someField2) // always safe } } ``` Take note that `CoreStoreObject`s implement the `ObjectRepresentation` protocol, which gives you access to different wrappers depending on how you need to use the instances: - `object.asPublisher()`: returns an `ObjectPublisher<T>`, which is the recommended type for passing around a live instance. You can [observe this instance for live changes](https://github.com/JohnEstropia/CoreStore#objectpublisherreactive). - `object.asSnapshot()`: returns an `ObjectSnapshot<T>`, which is a hard-copy of all field values (except relationships, which still need care when working with multithreads) When you need to convert from one type to another between threads, these are also available: - `object.asPublisher(in: dataStack)`: if somehow you have a background-thread instance you want to create an observable type from the main thread - `object.asReadOnly(in dataStack)`: if somehow you have a background-thread instance you want to get a read-only instance from `DataStack's thread (i.e. the main thread) - `object.asEditable(in: transaction)`: if you have an object you'd want to edit inside a transaction - `object.asSnapshot(in: dataStack)` and `object.asSnapshot(in: transaction)`: create snapshots of an object coming from another thread
Author
Owner

@jimbengtsson92 commented on GitHub (Feb 16, 2023):

@JohnEstropia Thank you for the quick and good response 👍 I will try these out!

@jimbengtsson92 commented on GitHub (Feb 16, 2023): @JohnEstropia Thank you for the quick and good response 👍 I will try these out!
Author
Owner

@jimbengtsson92 commented on GitHub (Feb 23, 2023):

@JohnEstropia I reverted my change so both all transactions and fetches are made on an AsynchronousDataTransaction and this fixed my problem.

However, I have seen the same crash when using CoreStore in both our WatchOS app and in its complication. I assume this is since they both use the same file to setup the SQLiteStore for the DataStack (which we want in order to be able to share the data between the watch and its complication), but I don't think this can be used as it is in a safe way since I think the transaction's background serial queue is not shared between the WatchOS extension's CoreStore and the complication's CoreStore. I don't think there is a way to use the same instance of the CoreStore between these 2, or I have at least not found a way to do it.

Do you think that also in this scenario it is best to use an ObjectPublisher<T>/ObjectSnapshot<T> in order to avoid this problem?

@jimbengtsson92 commented on GitHub (Feb 23, 2023): @JohnEstropia I reverted my change so both all transactions **and** fetches are made on an `AsynchronousDataTransaction` and this fixed my problem. However, I have seen the same crash when using `CoreStore` in both our WatchOS app and in its complication. I assume this is since they both use the same file to setup the `SQLiteStore` for the `DataStack` (which we want in order to be able to share the data between the watch and its complication), but I don't think this can be used as it is in a safe way since I think the `transaction`'s background serial queue is not shared between the WatchOS extension's `CoreStore` and the complication's `CoreStore`. I don't think there is a way to use the same instance of the `CoreStore` between these 2, or I have at least not found a way to do it. Do you think that also in this scenario it is best to use an `ObjectPublisher<T>`/`ObjectSnapshot<T>` in order to avoid this problem?
Author
Owner

@jimbengtsson92 commented on GitHub (Feb 23, 2023):

Edit: I probably missed the object property of a snapshot. It seems to work with snapshotWithRelationship.relationship.object?.asSnapshot(in: transaction) which gives me the snapshot.

And a follow up question now that I have started to look into using ObjectSnapshot<T> instead of CoreStoreObject.

  • object.asSnapshot(): returns an ObjectSnapshot<T>, which is a hard-copy of all field values (except relationships, which still need care when working with multithreads)

What is the best way to get the actual value of a Relationship.ToOne from an ObjectSnapshot<T>? When working with a CoreStoreObject I could simply use entityWithRelationship.relationship.value. Should I use snapshotWithRelationship.relationship.asSnapshot(in: dataStack)?.asReadOnly(in: dataStack)?

@jimbengtsson92 commented on GitHub (Feb 23, 2023): Edit: I probably missed the `object` property of a snapshot. It seems to work with `snapshotWithRelationship.relationship.object?.asSnapshot(in: transaction)` which gives me the snapshot. And a follow up question now that I have started to look into using `ObjectSnapshot<T>` instead of `CoreStoreObject`. > * `object.asSnapshot()`: returns an `ObjectSnapshot<T>`, which is a hard-copy of all field values (except relationships, which still need care when working with multithreads) What is the best way to get the actual value of a `Relationship.ToOne` from an `ObjectSnapshot<T>`? When working with a `CoreStoreObject` I could simply use `entityWithRelationship.relationship.value`. Should I use `snapshotWithRelationship.relationship.asSnapshot(in: dataStack)?.asReadOnly(in: dataStack)`?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#339