Inconsistent conflict resolution between production and tests
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 35/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Estancado
- Stack tecnológico
- sqlite, swift
Línea de trabajo
Start with SyncEngine.refreshLastKnownServerRecord(_:) and compare how saved records are represented by the mock server. Then read MergeConflictTests, especially clientRecordUpdatedBeforeServerRecord() and serverRecordEditedAfterClientAndProcessedAfterClient(), alongside PR #411. Done means production and test conflict-resolution behavior agree without relying on a polluted last-known server record.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Description
I’ve been trying to wrap my head around the implementation of the built-in “field-wise last edit wins” strategy, and I’m honestly having a hard time. To better understand what’s going on, I started documenting the current behavior in this document. While I still don’t have all the answers, I ran into some unexpected behavior that felt worth reporting, as it might help clarify the intended design.
It traces back to the purpose of the didSet flag in CKRecord.update(with:row:columnNames:parentForeignKey:), which is used during upserts. As I understand it, this logic attempts to restore values from the last-known server record onto an incoming server record. In my mental model, this should be superfluous (and really a no-op), as I was assuming the last-known server record represents the most recent record that was either accepted by or fetched from the server. In the three-way merge terms, this would be the ancestor.
However, while digging deeper, I found two spots in the codebase where a record is saved as the last-known server record before it has been accepted by the server. I went into more detail on these in the warning sections of the linked document. In particular, the premature save during send looked worrying to me, as it seems to break the mental model of the last-known server record.
When testing this with the sample apps, I noticed that the record was not actually being saved during send. Inspecting SyncEngine.refreshLastKnownServerRecord(_:) revealed that this was due to the check of the modificationDate between the saved last-known server record and the given record, which happened to be equal, as the given record was based off of the last-known server record.
In tests, however, the record is being saved. The reason appears to be that the mock server does not populate modificationDate on records it reports back to the sync engine. While CKRecord.modificationDate can’t be set directly, I was able to swizzle it and have the mock server simulate a save timestamp. With that change in place, behavior became closer to production but two MergeConflictTests started producing different results, as they had previously been operating on a polluted last-known server record:
clientRecordUpdatedBeforeServerRecord(): the final assertion changed fromisCompleted = 1 @ t=30toisCompleted = 1 @ t=60serverRecordEditedAfterClientAndProcessedAfterClient()(added in #353): the title changed from “Buy milk” to “Get milk”, which is incorrect, but at least aligned with the other test preferring local edits regardless of timestamps (as described in #354)
I might be wrong, but I don’t think the reconciliation from the last-known server record that doesn’t reflect the actual server state is an intended behavior specifically built for tests. Having different results between the production and test environment feels wrong, even though the broader question of the desired semantics still stands.
The most straightforward way to align both environments seems to be to have the mock server populate modificationDate on “saved” records. From there, we could iterate further. Is this how things are meant to work? Do you want me to submit a PR that allows the mock server to set the modification date? PR #411 submitted.
Checklist
- I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
- I have determined whether this bug is also reproducible in a vanilla GRDB project.
- If possible, I've reproduced the issue using the
mainbranch of this package. - This issue hasn't been addressed in an existing GitHub issue or discussion.
SQLiteData version information
1.4.1
Sharing version information
2.7.4
GRDB version information
7.8.0
Destination operating system
No response
Xcode version information
Version 26.2 (17C52)
Swift Compiler version information
swift-driver version: 1.127.14.1 Apple Swift version 6.2.3 (swiftlang-6.2.3.3.21 clang-1700.6.3.2)
Target: arm64-apple-macosx15.0
- Lenguaje dominante
- Swift
- Estrellas
- 1.9k
- Forks
- 154
- Merge medio
- 2 d 18 h
- PR fusionados (30 d)
- 1
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de pointfreeco/sqlite-data
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 50/100
pointfreeco/sqlite-data#557 · 2 comentarios ·
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 38/100
pointfreeco/sqlite-data#551 ·
-
Child records deleted locally when their parent's save failed with `quotaExceeded`Posiblemente ocupada @jalalawqati la tomó hace 22 días. Abiertobug
Dificultad 4/5 3-5 días Aptitud para principiantes 52/100
pointfreeco/sqlite-data#547 · 2 comentarios · 1 reacción ·
-
CloudKit data loss after deleting then re-inserting record with the same UUIDPosiblemente ocupada @jsutula la tomó hace 209 días. Abiertobug
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
pointfreeco/sqlite-data#418 · 2 comentarios ·
-
Violation of “last edit wins” conflict resolution strategyPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertobug
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
pointfreeco/sqlite-data#354 · 2 comentarios ·
Todos los issues de pointfreeco/sqlite-data
Issues similares
-
Native HTTP policy fixture rejects its pathless response URLPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
bug fixed (pending release)
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
vorssaint/vorssaint-utils#2810 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Notion AI: getSpaces response exceeds 5 MB plugin limit for multi-workspace accountsPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertoclawsweeper:fix-shape-clear clawsweeper:queueable-fix clawsweeper:source-repro impact:auth-provider issue-rating: 🦞 diamond lobster no-stale P2
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
steipete/CodexBar#4341 · 2 comentarios · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
kiosk_set_screensaver_mode ignored: mode is not passed through by the notification parserPosiblemente ocupada @bgoncal la tomó hoy. Abiertobug ios
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
home-assistant/iOS#6001 ·
Los mantenedores suelen responder en 1 día