Skip to content

Commit 46cfeac

Browse files
authored
Ensuer loser tree closes properly on error (#2132)
1 parent e26caf5 commit 46cfeac

File tree

2 files changed

+70
-12
lines changed

2 files changed

+70
-12
lines changed

pkg/util/loser/tree.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ func New[E any, S Sequence](sequences []S, maxVal E, at func(S) E, less func(E,
3838
t.nodes[i+nSequences].items = s
3939
if !t.moveNext(i + nSequences) { // Must call Next on each item so that At() has a value.
4040
if t.err != nil {
41+
// error during initialize, requires us to close sequences not touched yet and mark nodes as uninitialized
42+
for j := i + 1; j < nSequences; j++ {
43+
t.close(sequences[j])
44+
t.nodes[j+nSequences].index = -1
45+
}
4146
break
4247
}
4348
}

pkg/util/loser/tree_test.go

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"math"
66
"testing"
77

8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
811
"github.com/grafana/pyroscope/pkg/util/loser"
912
)
1013

@@ -13,6 +16,8 @@ type List struct {
1316
cur uint64
1417

1518
err error
19+
20+
closed int
1621
}
1722

1823
func NewList(list ...uint64) *List {
@@ -26,6 +31,9 @@ func (it *List) At() uint64 {
2631
func (it *List) Err() error { return it.err }
2732

2833
func (it *List) Next() bool {
34+
if it.err != nil {
35+
return false
36+
}
2937
if len(it.list) > 0 {
3038
it.cur = it.list[0]
3139
it.list = it.list[1:]
@@ -35,7 +43,12 @@ func (it *List) Next() bool {
3543
return false
3644
}
3745

46+
func (it *List) Close() { it.closed += 1 }
47+
3848
func (it *List) Seek(val uint64) bool {
49+
if it.err != nil {
50+
return false
51+
}
3952
for it.cur < val && len(it.list) > 0 {
4053
it.cur = it.list[0]
4154
it.list = it.list[1:]
@@ -150,34 +163,74 @@ func TestPush(t *testing.T) {
150163
}
151164

152165
func TestInitWithErr(t *testing.T) {
153-
l := NewList()
154-
l.err = errors.New("test")
155-
l2 := NewList(5, 6, 7, 8)
156-
tree := loser.New([]*List{l, l2}, math.MaxUint64, func(s *List) uint64 { return s.At() }, func(a, b uint64) bool { return a < b }, func(s *List) {})
157-
166+
lists := []*List{
167+
NewList(),
168+
NewList(5, 6, 7, 8),
169+
}
170+
lists[0].err = testErr
171+
tree := loser.New(lists, math.MaxUint64, func(s *List) uint64 { return s.At() }, func(a, b uint64) bool { return a < b }, func(s *List) { s.Close() })
158172
if tree.Next() {
159173
t.Errorf("Next() should have returned false")
160174
}
161-
if tree.Err() != l.err {
162-
t.Errorf("Err() should have returned %v, got %v", l.err, tree.Err())
175+
if tree.Err() != testErr {
176+
t.Errorf("Err() should have returned %v, got %v", testErr, tree.Err())
177+
}
178+
179+
tree.Close()
180+
for _, l := range lists {
181+
assert.Equal(t, l.closed, 1, "list %+#v not closed exactly once", l)
163182
}
183+
164184
}
165185

186+
var testErr = errors.New("test")
187+
166188
func TestErrDuringNext(t *testing.T) {
167-
l := NewList(5)
168-
l.err = errors.New("test")
169-
tree := loser.New([]*List{l}, math.MaxUint64, func(s *List) uint64 { return s.At() }, func(a, b uint64) bool { return a < b }, func(s *List) {})
189+
lists := []*List{
190+
NewList(5, 6),
191+
NewList(11, 12),
192+
}
193+
tree := loser.New(lists, math.MaxUint64, func(s *List) uint64 { return s.At() }, func(a, b uint64) bool { return a < b }, func(s *List) { s.Close() })
170194

195+
// no error for first element
171196
if !tree.Next() {
172197
t.Errorf("Next() should have returned true")
173198
}
199+
// now error for second
200+
lists[0].err = testErr
174201
if tree.Next() {
175202
t.Errorf("Next() should have returned false")
176203
}
177-
if tree.Err() != l.err {
178-
t.Errorf("Err() should have returned %v, got %v", l.err, tree.Err())
204+
if tree.Err() != testErr {
205+
t.Errorf("Err() should have returned %v, got %v", testErr, tree.Err())
179206
}
180207
if tree.Next() {
181208
t.Errorf("Next() should have returned false")
182209
}
210+
211+
tree.Close()
212+
for _, l := range lists {
213+
assert.Equal(t, l.closed, 1, "list %+#v not closed exactly once", l)
214+
}
215+
}
216+
217+
func TestErrInOneIterator(t *testing.T) {
218+
l := NewList()
219+
l.err = errors.New("test")
220+
221+
lists := []*List{
222+
NewList(5, 1),
223+
l,
224+
NewList(2, 4),
225+
}
226+
tree := loser.New(lists, math.MaxUint64, func(s *List) uint64 { return s.At() }, func(a, b uint64) bool { return a < b }, func(s *List) { s.Close() })
227+
228+
// error for first element
229+
require.False(t, tree.Next())
230+
assert.Equal(t, l.err, tree.Err())
231+
232+
tree.Close()
233+
for _, l := range lists {
234+
assert.Equal(t, l.closed, 1, "list %+#v not closed exactly once", l)
235+
}
183236
}

0 commit comments

Comments
 (0)