@@ -37,6 +37,22 @@ const (
37
37
RevtreeSubtestName = "revTree"
38
38
)
39
39
40
+ type BlipTesterClientConflictResolverType string
41
+
42
+ const (
43
+ ConflictResolverLastWriteWins BlipTesterClientConflictResolverType = "lww"
44
+
45
+ ConflictResolverDefault = ConflictResolverLastWriteWins
46
+ )
47
+
48
+ func (c BlipTesterClientConflictResolverType ) IsValid () bool {
49
+ switch c {
50
+ case ConflictResolverLastWriteWins :
51
+ return true
52
+ }
53
+ return false
54
+ }
55
+
40
56
type BlipTesterClientOpts struct {
41
57
ClientDeltas bool // Support deltas on the client side
42
58
Username string
@@ -62,6 +78,8 @@ type BlipTesterClientOpts struct {
62
78
63
79
// SourceID is used to define the SourceID for the blip client
64
80
SourceID string
81
+
82
+ ConflictResolver BlipTesterClientConflictResolverType
65
83
}
66
84
67
85
// defaultBlipTesterClientRevsLimit is the number of revisions sent as history when the client replicates - older revisions are not sent, and may not be stored.
@@ -281,6 +299,12 @@ func (cd *clientDoc) currentVersion(t testing.TB) *db.Version {
281
299
return & rev .version .CV
282
300
}
283
301
302
+ func (cd * clientDoc ) _currentVersion (t testing.TB ) * db.Version {
303
+ rev , err := cd ._latestRev ()
304
+ require .NoError (t , err )
305
+ return & rev .version .CV
306
+ }
307
+
284
308
type BlipTesterCollectionClient struct {
285
309
parent * BlipTesterClient
286
310
@@ -571,20 +595,41 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
571
595
doc .lock .Lock ()
572
596
defer doc .lock .Unlock ()
573
597
598
+ var incomingVersion DocVersion
574
599
var newVersion DocVersion
575
600
var hlv db.HybridLogicalVector
576
601
if btc .UseHLV () {
602
+ var incomingHLV * db.HybridLogicalVector
577
603
if revHistory != "" {
578
- existingVersion , _ , err : = db .ExtractHLVFromBlipMessage (revHistory )
604
+ incomingHLV , _ , err = db .ExtractHLVFromBlipMessage (revHistory )
579
605
require .NoError (btr .TB (), err , "error extracting HLV %q: %v" , revHistory , err )
580
- hlv = * existingVersion
606
+ hlv = * incomingHLV
581
607
}
582
- v , err := db .ParseVersion (revID )
608
+ incomingCV , err := db .ParseVersion (revID )
583
609
require .NoError (btr .TB (), err , "error parsing version %q: %v" , revID , err )
584
- newVersion = DocVersion {CV : v }
585
- require .NoError (btr .TB (), hlv .AddVersion (v ))
610
+ incomingVersion = DocVersion {CV : incomingCV }
611
+
612
+ clientCV := doc ._currentVersion (btc .TB ())
613
+ // incoming rev older than stored client version and comes from a different source - need to resolve
614
+ if incomingCV .Value < clientCV .Value && incomingCV .SourceID != clientCV .SourceID {
615
+ btc .TB ().Logf ("Detected conflict on pull of doc %q (clientCV:%v - incomingCV:%v incomingHLV:%#v)" , docID , clientCV , incomingCV , incomingHLV )
616
+ switch btc .BlipTesterClientOpts .ConflictResolver {
617
+ case ConflictResolverLastWriteWins :
618
+ // generate a new version for the resolution and write it to the remote HLV
619
+ v := db.Version {SourceID : fmt .Sprintf ("btc-%d" , btc .id ), Value : uint64 (time .Now ().UnixNano ())}
620
+ require .NoError (btc .TB (), hlv .AddVersion (v ), "couldn't add incoming HLV into client HLV" )
621
+ newVersion = DocVersion {CV : v }
622
+ hlv .SetPreviousVersion (incomingCV .SourceID , incomingCV .Value )
623
+ default :
624
+ btc .TB ().Fatalf ("Unknown conflict resolver %q - cannot resolve detected conflict" , btc .BlipTesterClientOpts .ConflictResolver )
625
+ }
626
+ } else {
627
+ newVersion = DocVersion {CV : incomingCV }
628
+ }
629
+ require .NoError (btc .TB (), hlv .AddVersion (newVersion .CV ), "couldn't add newVersion CV into doc HLV" )
586
630
} else {
587
631
newVersion = DocVersion {RevTreeID : revID }
632
+ incomingVersion = newVersion
588
633
}
589
634
590
635
docRev := clientDocRev {
@@ -614,12 +659,16 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
614
659
// store the new sequence for a replaced rev for tests waiting for this specific rev
615
660
doc ._seqsByVersions [replacedVersion ] = newClientSeq
616
661
}
617
- doc ._latestServerVersion = newVersion
662
+ // store the _incoming_ version - not newVersion - since we may have written a resolved conflict which will need pushing back
663
+ doc ._latestServerVersion = incomingVersion
618
664
619
665
if ! msg .NoReply () {
620
666
response := msg .Response ()
621
667
response .SetBody ([]byte (`[]` ))
622
668
}
669
+
670
+ // new sequence written, wake up changes feeds for push
671
+ btcr ._seqCond .Broadcast ()
623
672
return
624
673
}
625
674
@@ -794,24 +843,53 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
794
843
doc .lock .Lock ()
795
844
defer doc .lock .Unlock ()
796
845
797
- var newVersion DocVersion
846
+ var incomingVersion DocVersion
847
+ var versionToWrite DocVersion
798
848
var hlv db.HybridLogicalVector
799
849
if btc .UseHLV () {
850
+ var incomingHLV * db.HybridLogicalVector
800
851
if revHistory != "" {
801
- existingVersion , _ , err : = db .ExtractHLVFromBlipMessage (revHistory )
852
+ incomingHLV , _ , err = db .ExtractHLVFromBlipMessage (revHistory )
802
853
require .NoError (btr .TB (), err , "error extracting HLV %q: %v" , revHistory , err )
803
- hlv = * existingVersion
854
+ hlv = * incomingHLV
804
855
}
805
- v , err := db .ParseVersion (revID )
856
+ incomingCV , err := db .ParseVersion (revID )
806
857
require .NoError (btr .TB (), err , "error parsing version %q: %v" , revID , err )
807
- newVersion = DocVersion {CV : v }
808
- require .NoError (btr .TB (), hlv .AddVersion (v ))
858
+ incomingVersion = DocVersion {CV : incomingCV }
859
+
860
+ // fetch client's latest version to do conflict check and resolution
861
+ latestClientRev , err := doc ._latestRev ()
862
+ require .NoError (btc .TB (), err , "couldn't get latest revision for doc %q" , docID )
863
+ if latestClientRev != nil {
864
+ clientCV := latestClientRev .version .CV
865
+
866
+ // incoming rev older than stored client version and comes from a different source - need to resolve
867
+ if incomingCV .Value < clientCV .Value && incomingCV .SourceID != clientCV .SourceID {
868
+ btc .TB ().Logf ("Detected conflict on pull of doc %q (clientCV:%v - incomingCV:%v incomingHLV:%#v)" , docID , clientCV , incomingCV , incomingHLV )
869
+ switch btc .BlipTesterClientOpts .ConflictResolver {
870
+ case ConflictResolverLastWriteWins :
871
+ // local wins so write the local body back as a new resolved version (based on incoming HLV) to push
872
+ body = latestClientRev .body
873
+ v := db.Version {SourceID : fmt .Sprintf ("btc-%d" , btc .id ), Value : uint64 (time .Now ().UnixNano ())}
874
+ require .NoError (btc .TB (), hlv .AddVersion (v ), "couldn't add incoming HLV into client HLV" )
875
+ versionToWrite = DocVersion {CV : v }
876
+ hlv .SetPreviousVersion (incomingCV .SourceID , incomingCV .Value )
877
+ default :
878
+ btc .TB ().Fatalf ("Unknown conflict resolver %q - cannot resolve detected conflict" , btc .BlipTesterClientOpts .ConflictResolver )
879
+ }
880
+ } else {
881
+ // no conflict - accept incoming rev
882
+ versionToWrite = DocVersion {CV : incomingCV }
883
+ }
884
+ }
885
+ require .NoError (btc .TB (), hlv .AddVersion (versionToWrite .CV ), "couldn't add new CV into doc HLV" )
809
886
} else {
810
- newVersion = DocVersion {RevTreeID : revID }
887
+ versionToWrite = DocVersion {RevTreeID : revID }
888
+ incomingVersion = versionToWrite
811
889
}
812
890
docRev := clientDocRev {
813
891
clientSeq : newClientSeq ,
814
- version : newVersion ,
892
+ version : versionToWrite ,
815
893
HLV : hlv ,
816
894
body : body ,
817
895
message : msg ,
@@ -835,12 +913,16 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
835
913
// store the new sequence for a replaced rev for tests waiting for this specific rev
836
914
doc ._seqsByVersions [replacedVersion ] = newClientSeq
837
915
}
838
- doc ._latestServerVersion = newVersion
916
+ // store the _incoming_ version - not versionToWrite - since we may have written a resolved conflict which will need pushing back
917
+ doc ._latestServerVersion = incomingVersion
839
918
840
919
if ! msg .NoReply () {
841
920
response := msg .Response ()
842
921
response .SetBody ([]byte (`[]` ))
843
922
}
923
+
924
+ // new sequence written, wake up changes feeds for push
925
+ btcr ._seqCond .Broadcast ()
844
926
}
845
927
846
928
btr .bt .blipContext .HandlerForProfile [db .MessageGetAttachment ] = func (msg * blip.Message ) {
@@ -1007,6 +1089,12 @@ func (btcRunner *BlipTestClientRunner) NewBlipTesterClientOptsWithRT(rt *RestTes
1007
1089
if ! opts .AllowCreationWithoutBlipTesterClientRunner && ! btcRunner .initialisedInsideRunnerCode {
1008
1090
require .FailNow (btcRunner .TB (), "must initialise BlipTesterClient inside Run() method" )
1009
1091
}
1092
+ if opts .ConflictResolver == "" {
1093
+ opts .ConflictResolver = ConflictResolverDefault
1094
+ }
1095
+ if ! opts .ConflictResolver .IsValid () {
1096
+ require .FailNow (btcRunner .TB (), "invalid conflict resolver %q" , opts .ConflictResolver )
1097
+ }
1010
1098
if opts .SourceID == "" {
1011
1099
opts .SourceID = "blipclient"
1012
1100
}
0 commit comments