Skip to content

Commit 28e2c9d

Browse files
committed
db: fix Options.Clone()
`Clone()` was allowing a shallow copy of `*WALFailover`. This caused a lot of time debugging a test which unwittingly aliased the WAL failover FS and dir of two stores. This commit fixes this and adds a test that would have caught it. We should probably make `WALFailover` a non-pointer (with an `Enabled` field); even if we do, the new test is still valuable.
1 parent 4a06644 commit 28e2c9d

File tree

2 files changed

+68
-4
lines changed

2 files changed

+68
-4
lines changed

external_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ package pebble_test
77
import (
88
"bytes"
99
"io"
10+
randv1 "math/rand"
1011
"math/rand/v2"
12+
"reflect"
1113
"strings"
1214
"testing"
15+
"testing/quick"
1316
"time"
1417

1518
"github.com/cockroachdb/pebble"
@@ -246,3 +249,60 @@ func buildSeparatedValuesDB(
246249

247250
return db, keys
248251
}
252+
253+
func TestOptionsClone(t *testing.T) {
254+
seed := time.Now().UnixNano()
255+
t.Logf("Using seed %d", seed)
256+
rng := rand.New(rand.NewPCG(0, uint64(seed)))
257+
258+
a := metamorphic.RandomOptions(rng, metamorphic.TestkeysKeyFormat, nil /* custom opt parsers */).Opts
259+
b := a.Clone()
260+
if rng.IntN(2) == 0 {
261+
a, b = b, a
262+
}
263+
before := a.String()
264+
mangle(reflect.ValueOf(b).Elem(), rng)
265+
after := a.String()
266+
require.Equal(t, before, after)
267+
}
268+
269+
func mangle(v reflect.Value, rng *rand.Rand) {
270+
if !v.CanSet() {
271+
return
272+
}
273+
// Some of the time, generate a full new value.
274+
if rng.IntN(2) == 0 {
275+
genVal(v, rng)
276+
return
277+
}
278+
switch v.Type().Kind() {
279+
case reflect.Pointer:
280+
// If the pointer is to a type outside the pebble package, leave it alone;
281+
// Options.Clone() would never clone those objects.
282+
if v.Elem().CanSet() && v.Elem().Type().PkgPath() == reflect.TypeOf(pebble.Options{}).PkgPath() {
283+
mangle(v.Elem(), rng)
284+
}
285+
286+
case reflect.Struct:
287+
for i := 0; i < v.NumField(); i++ {
288+
mangle(v.Field(i), rng)
289+
}
290+
291+
case reflect.Slice, reflect.Array:
292+
for i := 0; i < v.Len(); i++ {
293+
mangle(v.Index(i), rng)
294+
}
295+
}
296+
}
297+
298+
func genVal(v reflect.Value, rng *rand.Rand) {
299+
defer func() {
300+
if r := recover(); r != nil {
301+
// Ignore errors generating values (caused by unexported fields).
302+
}
303+
}()
304+
newVal, ok := quick.Value(v.Type(), randv1.New(randv1.NewSource(rng.Int64())))
305+
if ok {
306+
v.Set(newVal)
307+
}
308+
}

options.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,11 +1491,15 @@ func (o *Options) initMaps() {
14911491

14921492
// Clone creates a shallow-copy of the supplied options.
14931493
func (o *Options) Clone() *Options {
1494-
n := &Options{}
1495-
if o != nil {
1496-
*n = *o
1494+
if o == nil {
1495+
return &Options{}
1496+
}
1497+
n := *o
1498+
if o.WALFailover != nil {
1499+
c := *o.WALFailover
1500+
n.WALFailover = &c
14971501
}
1498-
return n
1502+
return &n
14991503
}
15001504

15011505
func (o *Options) String() string {

0 commit comments

Comments
 (0)