Skip to content

Commit 7a638c6

Browse files
committed
db: assert that iterAlloc has been zeroed under invariants
Under invariants builds, assert that the iterAlloc struct has been zeroed out. We allow some fields that deliberately recycle fields to be exempted. Additionally we allow pointers to zeroed structs and slices with len()=0 and underlying capacity that is all zeroed.
1 parent bfa7636 commit 7a638c6

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

db.go

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"context"
1010
"fmt"
1111
"io"
12+
"reflect"
1213
"slices"
1314
"sync"
1415
"sync/atomic"
@@ -895,9 +896,9 @@ func (d *DB) commitWrite(b *Batch, syncWG *sync.WaitGroup, syncErr *error) (*mem
895896
}
896897

897898
type iterAlloc struct {
898-
keyBuf []byte
899-
boundsBuf [2][]byte
900-
prefixOrFullSeekKey []byte
899+
keyBuf []byte `invariants:"reused"`
900+
boundsBuf [2][]byte `invariants:"reused"`
901+
prefixOrFullSeekKey []byte `invariants:"reused"`
901902
batchState iteratorBatchState
902903
dbi Iterator
903904
merging mergingIter
@@ -906,6 +907,65 @@ type iterAlloc struct {
906907
levelsPositioned [3 + numLevels]bool
907908
}
908909

910+
// maybeAssertZeroed asserts that i is a "zeroed" value. See assertZeroed for
911+
// the definition of "zeroed". It's used to ensure we're properly zeroing out
912+
// memory before returning the iterAlloc to the shared pool.
913+
func (i *iterAlloc) maybeAssertZeroed() {
914+
if invariants.Enabled {
915+
v := reflect.ValueOf(i).Elem()
916+
if err := assertZeroed(v); err != nil {
917+
panic(err)
918+
}
919+
}
920+
}
921+
922+
// assertZeroed asserts that v is a "zeroed" value. A "zeroed" value is a value
923+
// that is:
924+
// - the Go zero value for its type, or
925+
// - a pointer to a "zeroed" value, or
926+
// - a slice of len()=0, with any values in the backing array being "zeroed
927+
// values", or
928+
// - a struct with all fields being "zeroed" values,
929+
// - a struct field explicitly marked with a "invariants:reused" tag.
930+
func assertZeroed(v reflect.Value) error {
931+
if v.IsZero() {
932+
return nil
933+
}
934+
typ := v.Type()
935+
switch typ.Kind() {
936+
case reflect.Pointer:
937+
return assertZeroed(v.Elem())
938+
case reflect.Slice:
939+
if v.Len() > 0 {
940+
return errors.AssertionFailedf("%s is not zeroed (%d len): %#v", typ.Name(), v.Len(), v)
941+
}
942+
resliced := v.Slice(0, v.Cap())
943+
for i := 0; i < resliced.Len(); i++ {
944+
if err := assertZeroed(resliced.Index(i)); err != nil {
945+
return errors.Wrapf(err, "[%d]", i)
946+
}
947+
}
948+
return nil
949+
case reflect.Struct:
950+
for i := 0; i < typ.NumField(); i++ {
951+
if typ.Field(i).Tag.Get("invariants") == "reused" {
952+
continue
953+
}
954+
if err := assertZeroed(v.Field(i)); err != nil {
955+
return errors.Wrapf(err, "%q", typ.Field(i).Name)
956+
}
957+
}
958+
return nil
959+
}
960+
return errors.AssertionFailedf("%s (%s) is not zeroed: %#v", typ.Name(), typ.Kind(), v)
961+
}
962+
963+
func newIterAlloc() *iterAlloc {
964+
buf := iterAllocPool.Get().(*iterAlloc)
965+
buf.maybeAssertZeroed()
966+
return buf
967+
}
968+
909969
var iterAllocPool = sync.Pool{
910970
New: func() interface{} {
911971
return &iterAlloc{}
@@ -996,7 +1056,7 @@ func (d *DB) newIter(
9961056

9971057
// Bundle various structures under a single umbrella in order to allocate
9981058
// them together.
999-
buf := iterAllocPool.Get().(*iterAlloc)
1059+
buf := newIterAlloc()
10001060
dbi := &buf.dbi
10011061
*dbi = Iterator{
10021062
ctx: ctx,

iterator.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2454,6 +2454,7 @@ func (i *Iterator) Close() error {
24542454
}
24552455
alloc.dbi = Iterator{}
24562456
alloc.merging = mergingIter{}
2457+
clear(mergingIterHeapItems[:cap(mergingIterHeapItems)])
24572458
alloc.merging.heap.items = mergingIterHeapItems
24582459
clear(alloc.mlevels[:])
24592460
clear(alloc.levels[:])
@@ -2861,7 +2862,7 @@ func (i *Iterator) CloneWithContext(ctx context.Context, opts CloneOptions) (*It
28612862
}
28622863
// Bundle various structures under a single umbrella in order to allocate
28632864
// them together.
2864-
buf := iterAllocPool.Get().(*iterAlloc)
2865+
buf := newIterAlloc()
28652866
dbi := &buf.dbi
28662867
*dbi = Iterator{
28672868
ctx: ctx,

scan_internal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func (d *DB) newInternalIter(
181181

182182
// Bundle various structures under a single umbrella in order to allocate
183183
// them together.
184-
buf := iterAllocPool.Get().(*iterAlloc)
184+
buf := newIterAlloc()
185185
dbi := &scanInternalIterator{
186186
ctx: ctx,
187187
db: d,

0 commit comments

Comments
 (0)