@@ -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
@@ -563,20 +587,41 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
563
587
doc .lock .Lock ()
564
588
defer doc .lock .Unlock ()
565
589
590
+ var incomingVersion DocVersion
566
591
var newVersion DocVersion
567
592
var hlv db.HybridLogicalVector
568
593
if btc .UseHLV () {
594
+ var incomingHLV * db.HybridLogicalVector
569
595
if revHistory != "" {
570
- existingVersion , _ , err : = db .ExtractHLVFromBlipMessage (revHistory )
596
+ incomingHLV , _ , err = db .ExtractHLVFromBlipMessage (revHistory )
571
597
require .NoError (btr .TB (), err , "error extracting HLV %q: %v" , revHistory , err )
572
- hlv = * existingVersion
598
+ hlv = * incomingHLV
573
599
}
574
- v , err := db .ParseVersion (revID )
600
+ incomingCV , err := db .ParseVersion (revID )
575
601
require .NoError (btr .TB (), err , "error parsing version %q: %v" , revID , err )
576
- newVersion = DocVersion {CV : v }
577
- require .NoError (btr .TB (), hlv .AddVersion (v ))
602
+ incomingVersion = DocVersion {CV : incomingCV }
603
+
604
+ clientCV := doc ._currentVersion (btc .TB ())
605
+ // incoming rev older than stored client version and comes from a different source - need to resolve
606
+ if incomingCV .Value < clientCV .Value && incomingCV .SourceID != clientCV .SourceID {
607
+ btc .TB ().Logf ("Detected conflict on pull of doc %q (clientCV:%v - incomingCV:%v incomingHLV:%#v)" , docID , clientCV , incomingCV , incomingHLV )
608
+ switch btc .BlipTesterClientOpts .ConflictResolver {
609
+ case ConflictResolverLastWriteWins :
610
+ // generate a new version for the resolution and write it to the remote HLV
611
+ v := db.Version {SourceID : fmt .Sprintf ("btc-%d" , btc .id ), Value : uint64 (time .Now ().UnixNano ())}
612
+ require .NoError (btc .TB (), hlv .AddVersion (v ), "couldn't add incoming HLV into client HLV" )
613
+ newVersion = DocVersion {CV : v }
614
+ hlv .SetPreviousVersion (incomingCV .SourceID , incomingCV .Value )
615
+ default :
616
+ btc .TB ().Fatalf ("Unknown conflict resolver %q - cannot resolve detected conflict" , btc .BlipTesterClientOpts .ConflictResolver )
617
+ }
618
+ } else {
619
+ newVersion = DocVersion {CV : incomingCV }
620
+ }
621
+ require .NoError (btc .TB (), hlv .AddVersion (newVersion .CV ), "couldn't add newVersion CV into doc HLV" )
578
622
} else {
579
623
newVersion = DocVersion {RevTreeID : revID }
624
+ incomingVersion = newVersion
580
625
}
581
626
582
627
docRev := clientDocRev {
@@ -606,12 +651,16 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
606
651
// store the new sequence for a replaced rev for tests waiting for this specific rev
607
652
doc ._seqsByVersions [replacedVersion ] = newClientSeq
608
653
}
609
- doc ._latestServerVersion = newVersion
654
+ // store the _incoming_ version - not newVersion - since we may have written a resolved conflict which will need pushing back
655
+ doc ._latestServerVersion = incomingVersion
610
656
611
657
if ! msg .NoReply () {
612
658
response := msg .Response ()
613
659
response .SetBody ([]byte (`[]` ))
614
660
}
661
+
662
+ // new sequence written, wake up changes feeds for push
663
+ btcr ._seqCond .Broadcast ()
615
664
return
616
665
}
617
666
@@ -786,24 +835,53 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
786
835
doc .lock .Lock ()
787
836
defer doc .lock .Unlock ()
788
837
789
- var newVersion DocVersion
838
+ var incomingVersion DocVersion
839
+ var versionToWrite DocVersion
790
840
var hlv db.HybridLogicalVector
791
841
if btc .UseHLV () {
842
+ var incomingHLV * db.HybridLogicalVector
792
843
if revHistory != "" {
793
- existingVersion , _ , err : = db .ExtractHLVFromBlipMessage (revHistory )
844
+ incomingHLV , _ , err = db .ExtractHLVFromBlipMessage (revHistory )
794
845
require .NoError (btr .TB (), err , "error extracting HLV %q: %v" , revHistory , err )
795
- hlv = * existingVersion
846
+ hlv = * incomingHLV
796
847
}
797
- v , err := db .ParseVersion (revID )
848
+ incomingCV , err := db .ParseVersion (revID )
798
849
require .NoError (btr .TB (), err , "error parsing version %q: %v" , revID , err )
799
- newVersion = DocVersion {CV : v }
800
- require .NoError (btr .TB (), hlv .AddVersion (v ))
850
+ incomingVersion = DocVersion {CV : incomingCV }
851
+
852
+ // fetch client's latest version to do conflict check and resolution
853
+ latestClientRev , err := doc ._latestRev ()
854
+ require .NoError (btc .TB (), err , "couldn't get latest revision for doc %q" , docID )
855
+ if latestClientRev != nil {
856
+ clientCV := latestClientRev .version .CV
857
+
858
+ // incoming rev older than stored client version and comes from a different source - need to resolve
859
+ if incomingCV .Value < clientCV .Value && incomingCV .SourceID != clientCV .SourceID {
860
+ btc .TB ().Logf ("Detected conflict on pull of doc %q (clientCV:%v - incomingCV:%v incomingHLV:%#v)" , docID , clientCV , incomingCV , incomingHLV )
861
+ switch btc .BlipTesterClientOpts .ConflictResolver {
862
+ case ConflictResolverLastWriteWins :
863
+ // local wins so write the local body back as a new resolved version (based on incoming HLV) to push
864
+ body = latestClientRev .body
865
+ v := db.Version {SourceID : fmt .Sprintf ("btc-%d" , btc .id ), Value : uint64 (time .Now ().UnixNano ())}
866
+ require .NoError (btc .TB (), hlv .AddVersion (v ), "couldn't add incoming HLV into client HLV" )
867
+ versionToWrite = DocVersion {CV : v }
868
+ hlv .SetPreviousVersion (incomingCV .SourceID , incomingCV .Value )
869
+ default :
870
+ btc .TB ().Fatalf ("Unknown conflict resolver %q - cannot resolve detected conflict" , btc .BlipTesterClientOpts .ConflictResolver )
871
+ }
872
+ } else {
873
+ // no conflict - accept incoming rev
874
+ versionToWrite = DocVersion {CV : incomingCV }
875
+ }
876
+ }
877
+ require .NoError (btc .TB (), hlv .AddVersion (versionToWrite .CV ), "couldn't add new CV into doc HLV" )
801
878
} else {
802
- newVersion = DocVersion {RevTreeID : revID }
879
+ versionToWrite = DocVersion {RevTreeID : revID }
880
+ incomingVersion = versionToWrite
803
881
}
804
882
docRev := clientDocRev {
805
883
clientSeq : newClientSeq ,
806
- version : newVersion ,
884
+ version : versionToWrite ,
807
885
HLV : hlv ,
808
886
body : body ,
809
887
message : msg ,
@@ -827,12 +905,16 @@ func (btr *BlipTesterReplicator) initHandlers(btc *BlipTesterClient) {
827
905
// store the new sequence for a replaced rev for tests waiting for this specific rev
828
906
doc ._seqsByVersions [replacedVersion ] = newClientSeq
829
907
}
830
- doc ._latestServerVersion = newVersion
908
+ // store the _incoming_ version - not versionToWrite - since we may have written a resolved conflict which will need pushing back
909
+ doc ._latestServerVersion = incomingVersion
831
910
832
911
if ! msg .NoReply () {
833
912
response := msg .Response ()
834
913
response .SetBody ([]byte (`[]` ))
835
914
}
915
+
916
+ // new sequence written, wake up changes feeds for push
917
+ btcr ._seqCond .Broadcast ()
836
918
}
837
919
838
920
btr .bt .blipContext .HandlerForProfile [db .MessageGetAttachment ] = func (msg * blip.Message ) {
@@ -999,6 +1081,12 @@ func (btcRunner *BlipTestClientRunner) NewBlipTesterClientOptsWithRT(rt *RestTes
999
1081
if ! opts .AllowCreationWithoutBlipTesterClientRunner && ! btcRunner .initialisedInsideRunnerCode {
1000
1082
require .FailNow (btcRunner .TB (), "must initialise BlipTesterClient inside Run() method" )
1001
1083
}
1084
+ if opts .ConflictResolver == "" {
1085
+ opts .ConflictResolver = ConflictResolverDefault
1086
+ }
1087
+ if ! opts .ConflictResolver .IsValid () {
1088
+ require .FailNow (btcRunner .TB (), "invalid conflict resolver %q" , opts .ConflictResolver )
1089
+ }
1002
1090
if opts .SourceID == "" {
1003
1091
opts .SourceID = "blipclient"
1004
1092
}
0 commit comments