Conflict that refuses to resolve


#1

I have an app that, on launch creates an empty database, and pulls from the sync gateway to populate it. There is one object that reports a conflict every time it starts up between a specific 5-XXXX revision and the current revision.
My conflict resolution routine, having determined that they do not have a common parent (since they are more than 10 generations apart), picks the later one, deletes the 5-XXXX revision and creates a new revision based on the current revision.
Looking at the document on the administration console shows that the new revision is created, but the 5-XXXX revision does not get tombstoned, and the next time I run it will report the exact same conflict with the new head revision.
Previous conflicts have been successfully resolved on this same object, but this one won’t die.
The bodies portion of the _sync element shows a body (identical to the current revision) for rev 5-XXXX, and 4 deleted revisions (from previous successful conflict resolutions).
Any idea why this thing wont die?


#2

My first guess is that your sync function is rejecting the deletion (tombstone) revision. This is a common mistake because by default a tombstone has no properties other than _deleted, so if you require specific properties in documents (like type) you’ll accidentally reject tombstones. In that case, explicitly check for a _deleted=true property first and skip the rest of your checks.


#3

At this point there is no logic in the sync function except a single channel call, so I’m pretty sure it isn’t that…

Also, previous conflict resolutions (with their associated deletions were all synced correctly.


#4

So it sounds like a client-side problem…

First, double-check that the deletion actually succeeded on the client. Check all return values, and try logging the rev ID of the deletion after it’s created.

Second, turn on logging (details vary by platform) to see what’s getting pushed to the server.


#5

Ok. I have figured out the problem, but it is an issue with the way revisions are named, and results in broken revision hierarchy.

The problem is that there was a 3-way conflict in generation 5. I selected one of the revisions and deleted the other two. Because both of the deletions ended up with the same content (i.e. nothing but _deleted}, their names hashed to the same value, but that revision can only have one parent, so only one of the two branches is actually closed. When I try to delete the other revision, it creates a tombstone with the same name as an existing one (terminating a different branch at the same generation).

This sounds like a problem on your end. Is there a way for me to include some content (even a random valued field) in the deletion to avoid the name conflict?

It seems to me that the naming means that there can be only one tombstone in any given generation, so the procedure would need to be multiple passes of delete one branch, and extend all the others, then repeat with the non-deleted branches until you are down to 1 branch.

A better solution would be to allow multiple tombstones in a generation…


#6

Possibly if tombstone revisions included the name of the revision they were deleting?

This would make deletions of the same revision from different nodes have the same name (and hence not generate a new conflict) but allow deletion of multiple revisions within a generation…


#7

What platform of CBL are you using? In the Objective-C codebase (iOS, Mac) the revision ID is based on the parent revision ID as well as the JSON, so this shouldn’t be possible.


#8

Android

Possible that I just managed to find a hash conflict… or the android algorithm is different…


#9

It’s an MD-5, which is not as good as SHA-1 (we need to fix that), but it should still be effectively impossible to find a collision without trying really hard to. It’s possible that the Java codebase forgot to include the parent rev ID in the hash. Please file an issue on Github. Thanks!


#10

I created an issue here: https://github.com/couchbase/couchbase-lite-java-core/issues/525


#11

Thanks. I was just researching the source for the android and iOS versions before creating one myself…

I noticed another discrepancy between them…

The android codebase currently generates the hash from:

  • a single byte containing the length of the previous key
  • a single byte flag indicating if this is a deletion
  • the attachments in sorted key order
  • the JSON

the iOS codebase generates the hash from:

  • a single byte containing the length of the previous key
  • the previous key converted to UTF8
  • a single byte flag indicating if this is a deletion
  • the JSON

It seems to me that if one wants to be able to sync a database to both android and iOS apps, that these two need to be the same, so it would seem that the iOS codebase also needs to hash the attachments…


#12

I created an issue for the attachments not being included in the rev id generation on the iOS side as well.


#13

No, the platforms don’t have to create identical revision IDs. (They’re all different from CouchDB, which IIRC uses an MD-5 of the serialization of the Erlang object.) The only thing that’s required is that different revisions have different revision IDs. It’s helpful if two clients create the same revID for an identical change, because it eliminates a no-op conflict, but it’s not required.

Sounds like a Java bug that it doesn’t include the previous revision string. The Objective-C version does include the attachments, because at that point they’re in the JSON as an _attachments object.


#14

Hi @delhard,

We fixed Revision ID generation issue (https://github.com/couchbase/couchbase-lite-java-core/issues/525) for CBL Android, and made build. If you have time, please try latest build from master.

If you are using Android Studio, please use “0.0.0-504” instead of “1.0.3”.
If you are using Eclipse, please download zip file from http://mobile.jenkins.couchbase.com/job/couchbase-lite-android-build/100/

Thank you very much for all your helps!!
Hideki


#15

I tried the fix and verified that the conflict now resolves correctly. Thanks for the speedy fix!


#16

I spoke too soon… I took a look at the code for the fix, and if doesn’t actually fix the issue. It seems to, since it generated a different hash than the previous version, and so was able to generate a tombstone at the same level as an existing one (with the old hash), but would still be unable to generate multiple tombstones at the same generation with the new hash.

I’ve added a comment describing the problem to the previous (now closed) bug.

Should I open a new issue?


#17

Hi @delhard,
Please reopen the ticket with additional information. Reopen button is located at bottom of page.
Thanks!


#18

@hideki — I think only team members have the ability to re-open an issue.


#19

The additional information is there in the comments, and I don’t have a reopen button…


#20

Hi @delhard,

Thank you very much for finding the problem. Next time I will check twice.

I sent PR. It will be merged into master branch after code review.

Thanks,
Hideki