9
9
"context"
10
10
"fmt"
11
11
"io"
12
+ "reflect"
12
13
"slices"
13
14
"sync"
14
15
"sync/atomic"
@@ -895,9 +896,9 @@ func (d *DB) commitWrite(b *Batch, syncWG *sync.WaitGroup, syncErr *error) (*mem
895
896
}
896
897
897
898
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"`
901
902
batchState iteratorBatchState
902
903
dbi Iterator
903
904
merging mergingIter
@@ -906,6 +907,65 @@ type iterAlloc struct {
906
907
levelsPositioned [3 + numLevels ]bool
907
908
}
908
909
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
+
909
969
var iterAllocPool = sync.Pool {
910
970
New : func () interface {} {
911
971
return & iterAlloc {}
@@ -996,7 +1056,7 @@ func (d *DB) newIter(
996
1056
997
1057
// Bundle various structures under a single umbrella in order to allocate
998
1058
// them together.
999
- buf := iterAllocPool . Get ().( * iterAlloc )
1059
+ buf := newIterAlloc ( )
1000
1060
dbi := & buf .dbi
1001
1061
* dbi = Iterator {
1002
1062
ctx : ctx ,
0 commit comments