-
Notifications
You must be signed in to change notification settings - Fork 78
Fix user modification timestamp. #386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| line: UInt = #line, | ||
| column: UInt = #column | ||
| ) async throws { | ||
| ) async throws -> SendRecordsCallback { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need this bit of infrastructure in the mocks to wiggle in between the moment a batch is sent out to iCloud and the moment the sentRecordZoneChanges delegate method is called.
| self.userModificationTime = #sql(""" | ||
| max(\(self.userModificationTime), \(lastKnownServerRecord.userModificationTime)) | ||
| """) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the actual fix.
| title🗓️: 2, | ||
| 🗓️: 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can write a failing test that shows data not properly syncing (we need to better support multiple sync engines in a test), but this at least shows the edit was made at the right time. Without the changes in this PR this timestamp was 1.
|
|
||
| extension CKRecord { | ||
| @TaskLocal static var printTimestamps = false | ||
| @TaskLocal static var printRecordChangeTag = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted a way to get visibility into record change tags.
|
Hi @mbrandonw, reading the description mentioning the step of preparing changes for upload and seeing the fix in
If so, I’d like to point out that this very spot exhibits different behavior between production and tests. I reported it in #356. The short version is that in tests, the record is always written, as the mock database doesn’t set CloudKit’s internal However, even when aligning the behavior between test and production environments, I wonder why we would ever want to save an in-flight record as the last-known server record before it’s confirmed by the server. I point it out in step 4 of scenario 2.1 in my document on the current behavior. It breaks the model for a potential three-way merge conflict resolution, as it would prevent using the last-known server record as the ancestor. |
No, the problematic call stack actually originates from
Yeah, it does seem like we should better emulate
Yeah, to be honest I don't remember why we do that. And removing that call to - isCompleted🗓️: 30,
+ isCompleted🗓️: 60,And I'm trying to think which is correct. The |
Hey @lukaskubanek, we discussed this at length yesterday, and our intention is to indeed eagerly store the updated The name of the field ( And further, our understanding is that this does not interfere with 3-way merging. It's our understanding that to perform 3-way merging we shouldn't even need anything from the locally cached records, and instead should only be using the server/client/ancestor records sent back from iCloud when I have just recently opened a PR (#389) to support ancestor records in the |
|
@mbrandonw I’m glad to see an initial discussion around 3-way merge support. That said, based on my earlier concept work, I have a few points I’d like to comment on.
I don’t think it’s sufficient to rely solely on CloudKit’s Basically, when there are concurrent changes to a single record on both the client and the server, it’s up to In #272 (reply in thread), I’ve shown a proof of the conflict-on-fetch scenario. It’s based on client-side custom conflict detection and demonstrates that it’s possible to observe such conflicts from the fetch path ( Based on my research, I don’t see a way to support proper 3-way merge conflict resolution without keeping track of an ancestor record locally.
Yes, I might have been shoehorning it into the ancestor role too much. That said, the name was the only thing I had to work with, as there is no documentation for these internal bits and it was hard to infer the intention. (And I can imagine it may have evolved while you were building the library.)
OK, that might be related to the I’m still not sure I’m getting it but it seems that you’re tracking the in-flight record to support the upsert logic, which then works with the server record, the in-flight record, and the database row to derive the field updates. Since there’s no ancestor, it’s more like a two-way merge with the client version split into two representations. Is that a good summary? In my concept work, I also ran into the question of whether this merge/consolidation logic really needs to run on every upsert, or only when a conflict is detected, i.e. when there are concurrent server and client changes. Server changes can be detected via change tags between the ancestor and server records. For client changes, I opted to use differences in modification timestamps between the ancestor record and I feel this comment is already getting quite long, so I’ll wrap it up here. I think the key point to discuss before diving in is the conflict-on-fetch scenario I described above and whether it’s something you’d consider addressing in SQLiteData. It rules out relying solely on CloudKit’s I’d be very interested to hear your thoughts and happy to discuss further. |
One more note on this behavior. I’ve noticed that since the mock database doesn’t set When I apply my local changes to have the mock database set I think the first step here would be to address this issue so that we can work with the tests reliably. |
Yeah, we absolutely want to fix this in the mock database, and we should do it soon. |
If a write is made to the database while a batch of records is being prepared and sent to iCloud, it is possible for us to accidentally cache an old timestamp on the the records. That could prevent other devices from updating their local data with fresh server data.