mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-11 20:00:30 +01:00
Crash When Accessing Required Value #339
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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:
We're using a
ListMonitorto populate a CollectionView.It crashes force unwrapping the value in
Value.swiftln. 155:The property in question is declared like so:
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.
@JohnEstropia commented on GitHub (Aug 19, 2020):
Without any other info my guesses would be
Can you share some snippets how the
tokenproperty is being used?@dylanmansur commented on GitHub (Aug 19, 2020):
Sure -
The class it is implemented in is more or less a
UICollectionViewDatasourcewrapper for aListMonitor.In that class, we set up the
ListMonitor:And add ourself as an observer, conforming to
ListObjectObserverand using the diffing methods to keep track of where we need to animate theUICollectionViewupdates. (I can post some of this, but there's nothing jumping out to me about it).The
RecentResourceEntityobject has an optional relationship --topic--- that has the property in question on it. The relationship is declared like so:The crash is happening when we're dequeueing a
UICollectionViewCellincollectionView(_:cellForItemAt:). In that method, we're makingstructversions of the models (the.detach()method returns thestructcopy) to render the cells. It does this for any of its relationships if it finds an object on them: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.
@JohnEstropia commented on GitHub (Aug 19, 2020):
Do you load your
ListMonitorbefore or after the migration?@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.@JohnEstropia commented on GitHub (Aug 19, 2020):
May I see the
detach()implementation here?@dylanmansur commented on GitHub (Aug 19, 2020):
Sure -
The init is just this. All the omitted lines are just copying over more properties:
Same thing with the topic:
Again, all omitted values are just copying things over from the stored model. Mostly strings, ints, and bools.
We're not holding on to a reference to the source model or anything as far as I can tell.
@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.
By the way, you posted the stack trace above but what was the final error message?
@dylanmansur commented on GitHub (Aug 19, 2020):
Nothing is listed in the Organizer, but AppCenter is listing a
SIGTRAP.@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 = falseto the fetch this crash happens.Please let me know if you need more information.
@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
ObjectPublisherwrapper instead of theCoreStoreObjectsubclasses directly. I recommend you work with theCoreStoreObjectinstances 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:Take note that
CoreStoreObjects implement theObjectRepresentationprotocol, which gives you access to different wrappers depending on how you need to use the instances:object.asPublisher(): returns anObjectPublisher<T>, which is the recommended type for passing around a live instance. You can observe this instance for live changes.object.asSnapshot(): returns anObjectSnapshot<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 threadobject.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 transactionobject.asSnapshot(in: dataStack)andobject.asSnapshot(in: transaction): create snapshots of an object coming from another thread@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 23, 2023):
@JohnEstropia I reverted my change so both all transactions and fetches are made on an
AsynchronousDataTransactionand this fixed my problem.However, I have seen the same crash when using
CoreStorein both our WatchOS app and in its complication. I assume this is since they both use the same file to setup theSQLiteStorefor theDataStack(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 thetransaction's background serial queue is not shared between the WatchOS extension'sCoreStoreand the complication'sCoreStore. I don't think there is a way to use the same instance of theCoreStorebetween 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):
Edit: I probably missed the
objectproperty of a snapshot. It seems to work withsnapshotWithRelationship.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 ofCoreStoreObject.What is the best way to get the actual value of a
Relationship.ToOnefrom anObjectSnapshot<T>? When working with aCoreStoreObjectI could simply useentityWithRelationship.relationship.value. Should I usesnapshotWithRelationship.relationship.asSnapshot(in: dataStack)?.asReadOnly(in: dataStack)?