Skip to content

Commit ed06469

Browse files
authored
CBL-7841: Handle conflict when revision history appears broken (#2489)
* CBL-7841: Handle conflict when revision history appears broken This is a fallback for cases where the general comparison algorithm yields a Conflict while applying a remote revision to the local document. The target scenario is a deleted document being resurrected in CBS -- when that happens, the resurrected document has no history prior to the deletion.
1 parent cabd447 commit ed06469

3 files changed

Lines changed: 60 additions & 0 deletions

File tree

LiteCore/Database/VectorDocument.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,47 @@ namespace litecore {
428428
} else {
429429
// Compare it with the current document revision:
430430
order = VersionVecWithLegacy::compare(newVers, curVers);
431+
432+
// CBL-7841: Handle the case where the revision history may appear broken.
433+
// This happens when a deleted doc is resurrected on the CB Server
434+
// without going through SGW. Manifested here, 'order' as yielded by the
435+
// above comparison shows Conflict. However, we want to resolve it here by simply
436+
// accepting the new version from the remote, provided the following conditions are
437+
// true:
438+
// 1. Conflict with a genuine remote (remote != RemoteID::Local)
439+
// 2. curVers is a tombstone
440+
// 3. Remote revision is available at `remote`
441+
// 4. curVers == RemoteRevision(remote)
442+
// 5. curVers.vector shares no version with newVers.vector
443+
// 6. curVers.legacy[0] is not in newVers.legacy (RevTree parent)
444+
445+
// Make a block that we can "break" out of if any of the above conditions fail.
446+
do {
447+
// 1. Conflict with genuine remote
448+
if ( order != kConflicting || remote == RemoteID::Local ) break;
449+
450+
// 2. curVers is a tombstone
451+
if ( !(_doc.flags() & DocumentFlags::kDeleted) ) break;
452+
453+
// 3. Remote revision is available
454+
optional<Revision> rev = _doc.loadRemoteRevision(remote);
455+
if ( !rev ) break;
456+
457+
// 4. curVers == RemoteRevision(remote)
458+
VersionVecWithLegacy lastSynced{rev->revID};
459+
if ( curVers.legacy != lastSynced.legacy || curVers.vector != lastSynced.vector ) break;
460+
461+
// 5. curVers.vector shares no version with newVers.vector
462+
if ( curVers.vector.sharesVersion(newVers.vector) ) break;
463+
464+
// 6. curVers.legacy[0] is not in newVers.legacy (RevTree parent)
465+
if ( !curVers.legacy.empty()
466+
&& std::ranges::find(newVers.legacy, curVers.legacy[0]) != newVers.legacy.end() )
467+
break;
468+
469+
order = kNewer;
470+
} while ( false );
471+
431472
logPutExisting(curVers, newVers, order, remote);
432473

433474
// Check for no-op or conflict:

LiteCore/RevTrees/VersionVector.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,22 @@ namespace litecore {
278278
return true;
279279
}
280280

281+
bool VersionVector::sharesVersion(const VersionVector& other) const {
282+
unordered_map<SourceID, logicalTime> myVersions;
283+
optional<Version> myExtraVersion; // A merge version may share author with the current.
284+
for ( auto& v : _vers ) {
285+
if ( !myVersions.insert({v.author(), v.time()}).second ) myExtraVersion = v;
286+
}
287+
288+
for ( auto& v : other._vers ) {
289+
auto i = myVersions.find(v.author());
290+
if ( i != myVersions.end() && i->second == v.time() ) return true;
291+
if ( myExtraVersion && *myExtraVersion == v ) return true;
292+
}
293+
294+
return false;
295+
}
296+
281297
#pragma mark - MODIFICATION:
282298

283299
void VersionVector::prune(size_t maxCount, logicalTime before) {

LiteCore/RevTrees/VersionVector.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ namespace litecore {
152152

153153
bool operator>=(const Version& v) const { return compareTo(v) != kOlder; }
154154

155+
// Whether this and `other` share a version.
156+
bool sharesVersion(const VersionVector& other) const;
157+
155158
using CompareBySourceFn = function_ref<bool(SourceID, logicalTime, logicalTime)>;
156159

157160
/** For each SourceID found in either `this` or `other`, calls the callback with that ID

0 commit comments

Comments
 (0)