Should save invocation with FAIL_ON_CONFLICT failed when the replicator save a new document?

Hello :wave:

I have a question about how FAIL_ON_CONFLICT work.
Imagine the following scenario :

  1. There is a document in Couchbase Lite, called Rev1. This document is currently used in the application memory.

  2. A changed is made from another device, sync with the remote database and then pulled into our first device. The replicator does not find any conflict with the local document, so the application saves the remote document.

  3. The application saved the document in memory with the option FAIL_ON_CONFLICT. In theory and how I understand it, the save should failed because the document has been updated in parallel.

private fun updateEntityInternal(entity: AndroidEntity): Boolean {
     val document = db.getDocument(entity.id)
            ?.toMutable()
            ?.apply {
                setData(entity.toMap())
            } ?: MutableDocument(entity.id, entity.toMap())

     return db.save(document, FAIL_ON_CONFLICT) // Return true instead after a replication
 }

In my case, the document is updated, but it should not. Did I understand properly how FAIL_ON_CONFLICT work or did I miss something ?

Maybe I missed it, but this is not a scenario I have read about in the blog post.

Thanks in advance for your response

Yes, your understanding FAIL_ON_CONFLICT is correct. How were you able to confirm that rev2 was in fact persisted in local database before the save was attempted ? Would it be possible that it came in after the save went through. Check the logs in debug mode which should give some hints

At first, I thought about that but the time between the save and the conflict resolution can be quite long (it depends of a human action).

This is the log during the replication (998df32d-bb5a-41e0-a6f7-fc5be1e60059 is the document ID) :

16:43:26.346 V/CouchbaseLite/DATABASE: {DB#20} Saved '998df32d-bb5a-41e0-a6f7-fc5be1e60059' rev #177-021cf28f2d537cfc84ddd2adfb32ae156a7b6305 as seq 7698
16:43:26.467 V/CouchbaseLite/DATABASE: {DB#20} Saved '998df32d-bb5a-41e0-a6f7-fc5be1e60059' rev #178-93c185351f53bfba53832f199f594698b8c386d6 as seq 7699
16:43:26.691 I/CouchbaseLite/REPLICATOR: Replicator{@0x5120dcf(<*>),Database{@0xb616571, name='VISITE_DB'} => URLEndpoint{url=wss://xxxxxxxxxxxxxxx}}: pulled conflicting version of '998df32d-bb5a-41e0-a6f7-fc5be1e60059'
16:43:26.765 W/CouchbaseLite/DATABASE: Unable to select conflicting revision for doc '998df32d-bb5a-41e0-a6f7-fc5be1e60059'. Skipping.
16:43:26.766 I/CouchbaseLite/REPLICATOR: Conflict resolved: 998df32d-bb5a-41e0-a6f7-fc5be1e60059
16:43:26.769 D/DocumentReplicationLoggerListener: Document created or modified notification received: 998df32d-bb5a-41e0-a6f7-fc5be1e60059
16:43:27.644 D/DatabaseManager$pushReplicationFilter: Document 998df32d-bb5a-41e0-a6f7-fc5be1e60059 allowed to be pushed by filters
16:43:27.696 I/CouchbaseLite/REPLICATOR: {N8litecore4repl6PusherE#671} Proposed rev '998df32d-bb5a-41e0-a6f7-fc5be1e60059' #178-93c185351f53bfba53832f199f594698b8c386d6 (ancestor 176-2fec84831c446a4d2f8fe204d98f835143cc036b) conflicts with newer server revision
16:43:27.697 I/CouchbaseLite/REPLICATOR: {N8litecore4repl6PusherE#671} Will try again if remote rev of '998df32d-bb5a-41e0-a6f7-fc5be1e60059' is updated
16:43:27.907 V/CouchbaseLite/REPLICATOR: {N8litecore4repl11IncomingRevE#686} Received revision '998df32d-bb5a-41e0-a6f7-fc5be1e60059' #206-f9b98ac0d38b831586a1fa71f3ba608c (seq '968540')
16:43:27.912 D/DatabaseManager$pullReplicationFilter: Document 998df32d-bb5a-41e0-a6f7-fc5be1e60059 allowed to be pulled by filters
16:43:27.938 I/CouchbaseLite/DATABASE: c4doc_put detected server-side branch-switch: "998df32d-bb5a-41e0-a6f7-fc5be1e60059" 176-2fec84831c446a4d2f8fe204d98f835143cc036b to 206-f9b98ac0d38b831586a1fa71f3ba608c; doing nothing
16:43:27.942 V/CouchbaseLite/DATABASE: {DB#688} Saved '998df32d-bb5a-41e0-a6f7-fc5be1e60059' rev #206-f9b98ac0d38b831586a1fa71f3ba608c as seq 7704
16:43:27.942 V/CouchbaseLite/REPLICATOR: {N8litecore4repl8InserterE#687}     {'998df32d-bb5a-41e0-a6f7-fc5be1e60059' #206-f9b98ac0d38b831586a1fa71f3ba608c <- 205-d6e8df6cc95a5d43e3be543dd9599ae85d89092b,204-9b29f616b30b52ed44ad81951b7424d72b7ee924,203-0ebc1b6a9004742ed7b0d8f5377f465d23a8eecd,202-eb8f8c5ae4f2c38f98e7281a7213e9b4ca8a76a6,201-9fe6710f405639a69d5720d374946c0fa8ef09d5,200-7739c919dd688181c2fd104a85a67b4483d6efe4,199-4011a72752d715def6cdea9049cc0008ddf5544e,198-1a3fc818ea366da4d426ecfc1d0c6b7ecd73bb5e,197-b320a849f983743c1c9a5e7b2d5a39eb438d58d4,196-ac402d7d2cd60caf08163cff626a9152b1af173c,195-bba615e091551000e17c0c57f1ab7035c1a9d910,194-a3a57d1b0efd4a6b9746a77367475e0cbd20d547,193-6c15b4a983c5d499186866e0887f1cfcb35db6ac,192-3e4cd58d79e4f6ea026943e07fd57cc64c8dc00e,191-a6a1eb47f2e57de4d76353d43e4e8c857dcc5a37,190-26a4032e8d5e1e830b29c69587d7fff2ac68a003,189-9aae57db6090c1454fdc36f9f27501c31ef14703,188-f85e9ea284f0e2e7fa9d0693bf805a86b985282f,187-2701416f1e43fe7e073893345f39711c7cd20baa,186-ae2c5f46dd7f1d6b35553925c904e12eec99689b} seq 7704
16:43:27.942 I/CouchbaseLite/REPLICATOR: {N8litecore4repl8InserterE#687} Created conflict with '998df32d-bb5a-41e0-a6f7-fc5be1e60059' #206-f9b98ac0d38b831586a1fa71f3ba608c
16:43:27.948 I/CouchbaseLite/REPLICATOR: {Repl#668} documentEnded 998df32d-bb5a-41e0-a6f7-fc5be1e60059 206-f9b98ac0d38b831586a1fa71f3ba608c flags=20 (0/0)
16:43:28.059 I/CouchbaseLite/REPLICATOR: Replicator{@0x5120dcf(<*>),Database{@0xb616571, name='VISITE_DB'} => URLEndpoint{url=wss://xxxxxxxxxxxxxxx}}: pulled conflicting version of '998df32d-bb5a-41e0-a6f7-fc5be1e60059'
16:43:28.089 V/CouchbaseLite/DATABASE: Resolving doc '998df32d-bb5a-41e0-a6f7-fc5be1e60059' (local=178-93c185351f53bfba53832f199f594698b8c386d6 and remote=206-f9b98ac0d38b831586a1fa71f3ba608c) with resolver fr.o2.visite.core.db.conflictresolver.DocumentConflictResolverStrategy@6fbe15a
16:43:28.090 D/DocumentConflictResolverStrategy: Conflict detected on document [998df32d-bb5a-41e0-a6f7-fc5be1e60059]
16:43:28.150 D/VisiteConflictResolver: Conflict solved taking remote document based on specific rules [998df32d-bb5a-41e0-a6f7-fc5be1e60059]
16:43:28.154 V/CouchbaseLite/DATABASE: Conflict resolved as doc '998df32d-bb5a-41e0-a6f7-fc5be1e60059' rev 11-6c46baaae2d24acb32ca014c30aced8277d474f5
16:43:28.156 I/CouchbaseLite/REPLICATOR: Conflict resolved: 998df32d-bb5a-41e0-a6f7-fc5be1e60059

We can see that the conflict has been resolved (2 times, I guess there is some improvement in our code base) because of : Conflict resolved: 998df32d-bb5a-41e0-a6f7-fc5be1e60059. If the conflict has been resolved, the document has been saved, isn’t it ?

Here, after the user’s action :

17:00:57.412 I/CouchbaseLite/DATABASE: ... SELECT sequence, flags, 0, version, body FROM kv_default WHERE key=?
17:00:57.417 V/CouchbaseLite/DATABASE: {DB#20} begin transaction
17:00:57.417 I/CouchbaseLite/DATABASE: BEGIN
17:00:57.417 I/CouchbaseLite/DATABASE: ... SELECT sequence, flags, 0, version, body FROM kv_info WHERE key=?
17:00:57.434 I/CouchbaseLite/DATABASE: ... SELECT sequence, flags, 0, version, body FROM kv_default WHERE key=?
17:00:57.435 I/CouchbaseLite/DATABASE: ... SELECT lastSeq FROM kvmeta WHERE name=?
17:00:57.435 I/CouchbaseLite/DATABASE: ... UPDATE kv_default SET version=?, body=?, flags=?, sequence=? WHERE key=? AND sequence=?
17:00:57.440 V/CouchbaseLite/DATABASE: {DB#20} Saved '998df32d-bb5a-41e0-a6f7-fc5be1e60059' rev #12-b2bc05f9cddb198d79601008724390c935a926e5 as seq 7717
17:00:57.440 V/CouchbaseLite/DATABASE: {DB#20} commit transaction
17:00:57.440 I/CouchbaseLite/DATABASE: ... INSERT INTO kvmeta (name, lastSeq) VALUES (?, ?) ON CONFLICT (name) DO UPDATE SET lastSeq = excluded.lastSeq
17:00:57.445 I/CouchbaseLite/DATABASE: COMMIT
17:00:57.447 V/CouchbaseLite/REPLICATOR: {N8litecore4repl6PusherE#671} Database changed!
17:00:57.447 V/CouchbaseLite/REPLICATOR: {N8litecore4repl6PusherE#671} Notified of 1 db changes #7717 ... #7717
17:00:57.448 I/CouchbaseLite/DATABASE: ... SELECT sequence, flags, 0, version, body FROM kv_default WHERE key=?
17:00:57.449 I/CouchbaseLite/REPLICATOR: {N8litecore4repl6PusherE#671} Found 0 changes up to #7717
17:00:57.449 D/BaseDao: updateEntityInternal fr.o2.visite.domain.entity.Visite - true

D/BaseDao: updateEntityInternal fr.o2.visite.domain.entity.Visite - true tell us the result of the save method (here true).

There is a revision difference between both (rev-206 and rev-12). Do you think there is a link ? :thinking: