Skip to content

Commit a0955d2

Browse files
storage/cmdq: prevent iterator from escaping to heap
The iterator returned by `tree.MakeIter` was escaping to the heap for two reasons: 1. it was capturing a reference to the tree itself. 2. it was pointing a slice into its own array. This change addresses both of these problems and prevents the iterator from escaping when used. The fixes were: 1. copy the tree's root pointer reference instead of a reference to the tree itself. 2. avoid creating the self-referential slice reference. This mistakenly escapes because of golang/go#7921, which also caused issues with `bytes.Buffer` (https://golang.org/cl/133715). This change also adds a new benchmark which demonstrates whether `MakeIter` escapes or not: ``` name old time/op new time/op delta BTreeMakeIter-4 131ns ±14% 25ns ± 1% -81.23% (p=0.000 n=9+9) name old alloc/op new alloc/op delta BTreeMakeIter-4 144B ± 0% 0B -100.00% (p=0.000 n=10+10) name old allocs/op new allocs/op delta BTreeMakeIter-4 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) ``` Release note: None
1 parent 4a02b8c commit a0955d2

File tree

2 files changed

+91
-27
lines changed

2 files changed

+91
-27
lines changed

pkg/storage/cmdq/interval_btree.go

+67-27
Original file line numberDiff line numberDiff line change
@@ -589,10 +589,12 @@ func (t *btree) Set(c *cmd) {
589589
}
590590
}
591591

592-
// MakeIter returns a new iterator object. Note that it is safe for an
593-
// iterator to be copied by value.
592+
// MakeIter returns a new iterator object. Note that it is safe for an iterator
593+
// to be copied by value. However, it is not safe to make modification to the
594+
// tree after an iterator is created. If modifications are made, create a new
595+
// iterator.
594596
func (t *btree) MakeIter() iterator {
595-
return iterator{t: t, pos: -1}
597+
return iterator{r: t.root, pos: -1}
596598
}
597599

598600
// Height returns the height of the tree.
@@ -645,45 +647,87 @@ func (n *node) writeString(b *strings.Builder) {
645647
}
646648
}
647649

648-
// iterator is responsible for search and traversal within a btree.
649-
type iterator struct {
650-
t *btree
651-
n *node
652-
pos int16
650+
// iterStack represents a stack of (node, pos) tuples, which captures
651+
// iteration state as an iterator descends a btree.
652+
type iterStack struct {
653+
a iterStackArr
654+
aLen int16 // -1 when using s
653655
s []iterFrame
654-
sBuf [3]iterFrame // avoids allocation of s up to height 4
655-
o overlapScan
656656
}
657657

658+
// Used to avoid allocations for stacks below a certain size.
659+
type iterStackArr [3]iterFrame
660+
658661
type iterFrame struct {
659662
n *node
660663
pos int16
661664
}
662665

666+
func (is *iterStack) push(f iterFrame) {
667+
if is.aLen == -1 {
668+
is.s = append(is.s, f)
669+
} else if int(is.aLen) == len(is.a) {
670+
is.s = make([]iterFrame, int(is.aLen)+1, 2*int(is.aLen))
671+
copy(is.s, is.a[:])
672+
is.s[int(is.aLen)] = f
673+
is.aLen = -1
674+
} else {
675+
is.a[is.aLen] = f
676+
is.aLen++
677+
}
678+
}
679+
680+
func (is *iterStack) pop() iterFrame {
681+
if is.aLen == -1 {
682+
f := is.s[len(is.s)-1]
683+
is.s = is.s[:len(is.s)-1]
684+
return f
685+
}
686+
is.aLen--
687+
return is.a[is.aLen]
688+
}
689+
690+
func (is *iterStack) len() int {
691+
if is.aLen == -1 {
692+
return len(is.s)
693+
}
694+
return int(is.aLen)
695+
}
696+
697+
func (is *iterStack) reset() {
698+
if is.aLen == -1 {
699+
is.s = is.s[:0]
700+
} else {
701+
is.aLen = 0
702+
}
703+
}
704+
705+
// iterator is responsible for search and traversal within a btree.
706+
type iterator struct {
707+
r *node
708+
n *node
709+
pos int16
710+
s iterStack
711+
o overlapScan
712+
}
713+
663714
func (i *iterator) reset() {
664-
i.n = i.t.root
665-
i.s = i.s[:0]
715+
i.n = i.r
666716
i.pos = -1
717+
i.s.reset()
667718
i.o = overlapScan{}
668719
}
669720

670-
// descend descends into the node's child at position pos. It maintains a
671-
// stack of (parent, position) pairs so that the tree can be ascended again
672-
// later.
673721
func (i *iterator) descend(n *node, pos int16) {
674-
if i.s == nil {
675-
i.s = i.sBuf[:0]
676-
}
677-
i.s = append(i.s, iterFrame{n: n, pos: pos})
722+
i.s.push(iterFrame{n: n, pos: pos})
678723
i.n = n.children[pos]
679724
i.pos = 0
680725
}
681726

682727
// ascend ascends up to the current node's parent and resets the position
683728
// to the one previously set for this parent node.
684729
func (i *iterator) ascend() {
685-
f := i.s[len(i.s)-1]
686-
i.s = i.s[:len(i.s)-1]
730+
f := i.s.pop()
687731
i.n = f.n
688732
i.pos = f.pos
689733
}
@@ -763,7 +807,7 @@ func (i *iterator) Next() {
763807
if i.pos < i.n.count {
764808
return
765809
}
766-
for len(i.s) > 0 && i.pos >= i.n.count {
810+
for i.s.len() > 0 && i.pos >= i.n.count {
767811
i.ascend()
768812
}
769813
return
@@ -788,7 +832,7 @@ func (i *iterator) Prev() {
788832
if i.pos >= 0 {
789833
return
790834
}
791-
for len(i.s) > 0 && i.pos < 0 {
835+
for i.s.len() > 0 && i.pos < 0 {
792836
i.ascend()
793837
i.pos--
794838
}
@@ -921,10 +965,6 @@ func (i *iterator) findNextOverlap() {
921965
for {
922966
if i.pos > i.n.count {
923967
// Iterate up tree.
924-
if len(i.s) == 0 {
925-
// Should have already hit upper-bound constraint.
926-
panic("unreachable")
927-
}
928968
i.ascend()
929969
} else if !i.n.leaf {
930970
// Iterate down tree.

pkg/storage/cmdq/interval_btree_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,22 @@ func TestBTreeCmp(t *testing.T) {
516516
}
517517
}
518518

519+
func TestIterStack(t *testing.T) {
520+
f := func(i int) iterFrame { return iterFrame{pos: int16(i)} }
521+
var is iterStack
522+
for i := 1; i <= 2*len(iterStackArr{}); i++ {
523+
var j int
524+
for j = 0; j < i; j++ {
525+
is.push(f(j))
526+
}
527+
require.Equal(t, j, is.len())
528+
for j--; j >= 0; j-- {
529+
require.Equal(t, f(j), is.pop())
530+
}
531+
is.reset()
532+
}
533+
}
534+
519535
//////////////////////////////////////////
520536
// Benchmarks //
521537
//////////////////////////////////////////
@@ -594,6 +610,14 @@ func BenchmarkBTreeDeleteInsert(b *testing.B) {
594610
})
595611
}
596612

613+
func BenchmarkBTreeMakeIter(b *testing.B) {
614+
var tr btree
615+
for i := 0; i < b.N; i++ {
616+
it := tr.MakeIter()
617+
it.First()
618+
}
619+
}
620+
597621
func BenchmarkBTreeIterSeekGE(b *testing.B) {
598622
forBenchmarkSizes(b, func(b *testing.B, count int) {
599623
var spans []roachpb.Span

0 commit comments

Comments
 (0)