Repository navigation
Don't delete a child whose parent was never uploaded - #568
mario-luis-dev wants to merge 2 commits into
Conversation
|
Hi @mario-luis-dev, the sorting fix is a good one, but it can be simplified and I have done so here #571. The other problem is trickier, and I don't quite yet understand how CloudKit can actually receive child records before parent. Your tests are not very convincing because they are specifically going outside the normal operations of the sync engine to get a failing test. We will investigate this more. |
|
Thanks @mbrandonw, #571 is a cleaner fix for the sort. I'm happy to take the sorting change out of this PR and rebase what's left on top of yours. How children reached CloudKit before their parents In our case it was the sort. Here's what we saw. The setup was SQLiteData 1.12.0 on one simulator, in the development environment, with iCloud turned on for the first time into an empty zone and about 58,000 saves waiting:
This matches what our users had been reporting for a while: episodes they'd marked as watched coming back unwatched. The tests You're right about them. The first two build the failing state by hand: one sends only the child, the other removes the parent's pending save. With #571 in place, I don't have a sequence of ordinary writes and sends that causes the deletion through ordering alone. In the description I also listed a concurrent send (the Why I'd still guard the deletion The branch decides "the parent was deleted on the server" from an error that can also mean "the parent isn't on the server yet". In our case that guess was wrong every time. When it's wrong, the result is the worst kind of failure: a user-level delete that cascades, syncs to every device, and shows no error. Ordering also isn't the only way a parent can be missing. This part comes from reading the code and Apple's docs; I haven't reproduced it:
So suppose a parent's first save fails with any other error. If it would help, I can write that as a test that only touches the CloudKit boundary. The guard is narrow. It applies the |
|
Hi @mario-luis-dev, please summarize the output of your AI agent in your own words. That is a lot of text to essentially say that 1) the sort fix in #571 would have fixed your problem, and 2) theoretically there is another problem lurking in the shadows. And I have to take a lot of my own time to read through all of that text and extract out its essence. If there really is a problem in the library related to failed saves and batches, would it be possible to write a test that emulates reality and shows the problem? And if our mock CloudKit APIs are capable of showing the problem, can they be improved? |
|
Sorry about that, fair point. Yes, that's it: #571 fixes what we actually hit, so feel free to close this PR in favor of it. The second issue is only theory for now. I'll try to write a test that shows it using just your mock CloudKit APIs, extending them where needed, and I'll only come back if it really reproduces. |
Once the database holds 'storageQuota' records, saving a new record fails with 'quotaExceeded', like it does when the user's iCloud storage is full. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a save fails with a reference violation, the sync engine applies the child's 'ON DELETE' action, as if the parent had been deleted on another device. A parent whose own save failed with an error that is not retried, such as 'quotaExceeded', was never uploaded at all, so the next child saved under it was deleted. Apply the 'ON DELETE' action only when the server has confirmed the parent before (its last known server record has a change tag). Otherwise queue the parent and the child again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b51cc39 to
2963d4b
Compare
|
Actually, I wrote a test that emulates reality. I added a storage quota to Also, the first reminder never uploads at all, because nothing re-queues a save that failed with |
|
Hi @mario-luis-dev, for quota exceeded errors in particular we have #335 that we will be merging and releasing soon. |
When a save fails with a reference violation, the sync engine applies the child's
ON DELETEaction, assuming the parent was deleted on another device. The same error comes back when the parent was never uploaded, for example because its own save failed withquotaExceeded, which nothing retries. The child then gets deleted.This applies the
ON DELETEaction only when the server has confirmed the parent before (its last known server record has a change tag). Otherwise it queues the parent and the child again.To test it,
MockCloudDatabasegets astorageQuota: once it's full, saving a new record fails withquotaExceeded, like a full iCloud.addReminderAfterListFailedToSaveWithQuotaExceededfails on main and passes with this change, and the full suite passes.