浏览代码

fix(db): version vector serialisation :( (#10050)

ffs
Jakob Borg 6 月之前
父节点
当前提交
ddea2e449c
共有 4 个文件被更改,包括 91 次插入1 次删除
  1. 48 0
      internal/db/sqlite/db_test.go
  2. 7 0
      internal/db/sqlite/util.go
  3. 33 0
      internal/db/sqlite/util_test.go
  4. 3 1
      lib/protocol/vector.go

+ 48 - 0
internal/db/sqlite/db_test.go

@@ -1109,6 +1109,54 @@ func TestErrorWrap(t *testing.T) {
 	}
 }
 
+func TestStrangeDeletedGlobalBug(t *testing.T) {
+	// This exercises an edge case with serialisation and ordering of
+	// version vectors. It does not need to make sense, it just needs to
+	// pass.
+
+	t.Parallel()
+
+	sdb, err := OpenTemp()
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Cleanup(func() {
+		if err := sdb.Close(); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	// One remote device announces the original version of the file
+
+	file := genFile("test", 1, 1)
+	file.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 35494436325452, Value: 1742900373}}}
+	t.Log("orig", file.Version)
+	sdb.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{file})
+
+	// Another one announces a newer one that is deleted
+
+	del := file
+	del.SetDeleted(43)
+	del.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 55445057455644, Value: 1742918457}, {ID: 35494436325452, Value: 1742900373}}}
+	t.Log("del", del.Version)
+	sdb.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{del})
+
+	// We have an instance of the original file
+
+	sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file})
+
+	// Which one is the global? It should be the deleted one, clearly.
+
+	g, _, err := sdb.GetGlobalFile(folderID, "test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !g.Deleted {
+		t.Log(g)
+		t.Fatal("should be deleted")
+	}
+}
+
 func mustCollect[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) []T {
 	t.Helper()
 	return func(it iter.Seq[T], errFn func() error) []T {

+ 7 - 0
internal/db/sqlite/util.go

@@ -7,9 +7,11 @@
 package sqlite
 
 import (
+	"cmp"
 	"database/sql/driver"
 	"errors"
 	"iter"
+	"slices"
 
 	"github.com/jmoiron/sqlx"
 	"github.com/syncthing/syncthing/internal/gen/bep"
@@ -71,6 +73,11 @@ func (v *dbVector) Scan(value any) error {
 	if err != nil {
 		return wrap(err)
 	}
+
+	// This is only necessary because I messed up counter serialisation and
+	// thereby ordering in 2.0.0 betas, and can be removed in the future.
+	slices.SortFunc(vec.Counters, func(a, b protocol.Counter) int { return cmp.Compare(a.ID, b.ID) })
+
 	v.Vector = vec
 
 	return nil

+ 33 - 0
internal/db/sqlite/util_test.go

@@ -0,0 +1,33 @@
+// Copyright (C) 2025 The Syncthing Authors.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
+// You can obtain one at https://mozilla.org/MPL/2.0/.
+
+package sqlite
+
+import (
+	"testing"
+
+	"github.com/syncthing/syncthing/lib/protocol"
+)
+
+func TestDbvector(t *testing.T) {
+	vec := protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 7}, {ID: 123456789, Value: 42424242}}}
+	dbVec := dbVector{vec}
+	val, err := dbVec.Value()
+	if err != nil {
+		t.Fatal(val)
+	}
+
+	var dbVec2 dbVector
+	if err := dbVec2.Scan(val); err != nil {
+		t.Fatal(err)
+	}
+
+	if !dbVec2.Vector.Equal(vec) {
+		t.Log(vec)
+		t.Log(dbVec2.Vector)
+		t.Fatal("should match")
+	}
+}

+ 3 - 1
lib/protocol/vector.go

@@ -31,7 +31,9 @@ func (v *Vector) String() string {
 		if i > 0 {
 			buf.WriteRune(',')
 		}
-		fmt.Fprintf(&buf, "%x:%d", c.ID, c.Value)
+		var idbs [8]byte
+		binary.BigEndian.PutUint64(idbs[:], uint64(c.ID))
+		fmt.Fprintf(&buf, "%x:%d", idbs, c.Value)
 	}
 	return buf.String()
 }