Skip to content

Commit 3eb304d

Browse files
committed
pebble: add missing cleanup in a NewExternalIter error case
If creating a point or range key iterator fails, we leak some iterators that were already opened. This immediately leads to a BufferPool-related panic when closing the `Iterator`. This change adds an enhancement to a test which reproduces the panic and patches up the error paths. Informs github.com/cockroachdb/cockroach/issues/141606
1 parent 7598e14 commit 3eb304d

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

external_iterator.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,21 @@ func createExternalPointIter(
178178
pointIter, err = r.NewPointIter(
179179
ctx, transforms, it.opts.LowerBound, it.opts.UpperBound, nil, /* BlockPropertiesFilterer */
180180
sstable.NeverUseFilterBlock, readEnv, sstable.MakeTrivialReaderProvider(r))
181-
if err != nil {
182-
return nil, err
181+
if err == nil {
182+
rangeDelIter, err = r.NewRawRangeDelIter(ctx, sstable.FragmentIterTransforms{
183+
SyntheticSeqNum: sstable.SyntheticSeqNum(seqNum),
184+
}, readEnv)
183185
}
184-
rangeDelIter, err = r.NewRawRangeDelIter(ctx, sstable.FragmentIterTransforms{
185-
SyntheticSeqNum: sstable.SyntheticSeqNum(seqNum),
186-
}, readEnv)
187186
if err != nil {
187+
if pointIter != nil {
188+
pointIter.Close()
189+
}
190+
for i := range mlevels {
191+
mlevels[i].iter.Close()
192+
if mlevels[i].rangeDelIter != nil {
193+
mlevels[i].rangeDelIter.Close()
194+
}
195+
}
188196
return nil, err
189197
}
190198
mlevels = append(mlevels, mergingIterLevel{
@@ -241,9 +249,14 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) error {
241249
for _, r := range readers {
242250
transforms := sstable.FragmentIterTransforms{SyntheticSeqNum: sstable.SyntheticSeqNum(seqNum)}
243251
seqNum--
244-
if rki, err := r.NewRawRangeKeyIter(ctx, transforms, readEnv); err != nil {
252+
rki, err := r.NewRawRangeKeyIter(ctx, transforms, readEnv)
253+
if err != nil {
254+
for _, iter := range rangeKeyIters {
255+
iter.Close()
256+
}
245257
return err
246-
} else if rki != nil {
258+
}
259+
if rki != nil {
247260
rangeKeyIters = append(rangeKeyIters, rki)
248261
}
249262
}

external_iterator_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"bytes"
99
"fmt"
1010
"math/rand/v2"
11+
"slices"
1112
"testing"
1213

1314
"github.com/cockroachdb/datadriven"
15+
"github.com/cockroachdb/errors"
1416
"github.com/cockroachdb/pebble/internal/testkeys"
1517
"github.com/cockroachdb/pebble/objstorage/objstorageprovider"
1618
"github.com/cockroachdb/pebble/sstable"
@@ -61,6 +63,7 @@ func TestExternalIterator(t *testing.T) {
6163
}
6264
}
6365
}
66+
testExternalIteratorInitError(t, o, &opts, files)
6467
it, err := NewExternalIter(o, &opts, files)
6568
require.NoError(t, err)
6669
return runIterCmd(td, it, true /* close iter */)
@@ -70,6 +73,45 @@ func TestExternalIterator(t *testing.T) {
7073
})
7174
}
7275

76+
// testExternalIteratorInitError tests error handling paths inside
77+
// NewExternalIter by injecting errors when reading files.
78+
//
79+
// See github.com/cockroachdb/cockroach/issues/141606 where an error during
80+
// initialization caused NewExternalIter to panic.
81+
func testExternalIteratorInitError(
82+
t *testing.T, o *Options, iterOpts *IterOptions, files [][]sstable.ReadableFile,
83+
) {
84+
files = slices.Clone(files)
85+
for i := range files {
86+
files[i] = slices.Clone(files[i])
87+
for j := range files[i] {
88+
files[i][j] = &flakyFile{ReadableFile: files[i][j]}
89+
}
90+
}
91+
92+
for iter := 0; iter < 100; iter++ {
93+
it, err := NewExternalIter(o, iterOpts, files)
94+
if err != nil {
95+
require.Contains(t, err.Error(), "flaky file")
96+
} else {
97+
it.Close()
98+
}
99+
}
100+
}
101+
102+
type flakyFile struct {
103+
sstable.ReadableFile
104+
}
105+
106+
func (ff *flakyFile) ReadAt(p []byte, off int64) (n int, err error) {
107+
if rand.IntN(10) == 0 {
108+
return 0, errors.New("flaky file")
109+
}
110+
return ff.ReadableFile.ReadAt(p, off)
111+
}
112+
113+
func (ff *flakyFile) Close() error { return nil }
114+
73115
func BenchmarkExternalIter_NonOverlapping_Scan(b *testing.B) {
74116
ks := testkeys.Alpha(6)
75117
opts := &Options{Comparer: testkeys.Comparer}

0 commit comments

Comments
 (0)