Skip to content

Commit

Permalink
sstable: detect double Close on sstable iterators
Browse files Browse the repository at this point in the history
A flag indicates if an iterator is in the pool; if we call `Close()`
twice and the iterator has not been reused yet, we panic.

We add a `CloseHelper` which can absorb multiple `Close()` calls,
which is convenient to use with `defer`.

Also update the `InternalIterator` documentation to reflect the
reality that multiple `Close()` calls are not supported by all
implementations.
  • Loading branch information
RaduBerinde committed Jan 30, 2024
1 parent eb8e9db commit 8471e8d
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 6 deletions.
30 changes: 30 additions & 0 deletions internal/base/close_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.

package base

import "io"

// CloseHelper wraps an io.Closer in a wrapper that ignores extra calls to
// Close. It is useful to ensure cleanup in error paths (using defer) without
// double-closing.
func CloseHelper(closer io.Closer) io.Closer {
return &closeHelper{
Closer: closer,
}
}

type closeHelper struct {
Closer io.Closer
}

// Close the underlying Closer, unless it was already closed.
func (h *closeHelper) Close() error {
closer := h.Closer
if closer == nil {
return nil
}
h.Closer = nil
return closer.Close()
}
6 changes: 4 additions & 2 deletions internal/base/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,10 @@ type InternalIterator interface {

// Close closes the iterator and returns any accumulated error. Exhausting
// all the key/value pairs in a table is not considered to be an error.
// It is valid to call Close multiple times. Other methods should not be
// called after the iterator has been closed.
//
// Once Close is called, the iterator should not be used again. Specific
// implementations may support multiple calls to Close (but no other calls
// after the first Close).
Close() error

// SetBounds sets the lower and upper bounds for the iterator. Note that the
Expand Down
13 changes: 11 additions & 2 deletions sstable/reader_iter_single_lvl.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ type singleLevelIterator struct {
lastBloomFilterMatched bool

hideObsoletePoints bool

// inPool is set to true before putting the iterator in the reusable pool;
// used to detect double-close.
inPool bool
}

// singleLevelIterator implements the base.InternalIterator interface.
Expand Down Expand Up @@ -203,6 +207,7 @@ func (i *singleLevelIterator) init(
i.endKeyInclusive, lower, upper = v.constrainBounds(lower, upper, false /* endInclusive */)
}

i.inPool = false
i.ctx = ctx
i.lower = lower
i.upper = upper
Expand Down Expand Up @@ -274,8 +279,9 @@ func (i *singleLevelIterator) setupForCompaction() {

func (i *singleLevelIterator) resetForReuse() singleLevelIterator {
return singleLevelIterator{
index: i.index.resetForReuse(),
data: i.data.resetForReuse(),
index: i.index.resetForReuse(),
data: i.data.resetForReuse(),
inPool: true,
}
}

Expand Down Expand Up @@ -1440,6 +1446,9 @@ func firstError(err0, err1 error) error {
// Close implements internalIterator.Close, as documented in the pebble
// package.
func (i *singleLevelIterator) Close() error {
if invariants.Enabled && i.inPool {
panic("Close called on interator in pool")
}
i.iterStats.close()
var err error
if i.closeHook != nil {
Expand Down
5 changes: 5 additions & 0 deletions sstable/reader_iter_two_lvl.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"

"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing"
)

Expand Down Expand Up @@ -165,6 +166,7 @@ func (i *twoLevelIterator) init(
i.endKeyInclusive, lower, upper = v.constrainBounds(lower, upper, false /* endInclusive */)
}

i.inPool = false
i.ctx = ctx
i.lower = lower
i.upper = upper
Expand Down Expand Up @@ -979,6 +981,9 @@ func (i *twoLevelIterator) skipBackward() (*InternalKey, base.LazyValue) {
// Close implements internalIterator.Close, as documented in the pebble
// package.
func (i *twoLevelIterator) Close() error {
if invariants.Enabled && i.inPool {
panic("Close called on interator in pool")
}
i.iterStats.close()
var err error
if i.closeHook != nil {
Expand Down
5 changes: 3 additions & 2 deletions tool/sstable.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,8 @@ func (s *sstableT) runScan(cmd *cobra.Command, args []string) {
fmt.Fprintf(stderr, "%s%s\n", prefix, err)
return
}
defer iter.Close()
iterCloser := base.CloseHelper(iter)
defer iterCloser.Close()
key, value := iter.SeekGE(s.start, base.SeekGEFlagsNone)

// We configured sstable.Reader to return raw tombstones which requires a
Expand Down Expand Up @@ -532,7 +533,7 @@ func (s *sstableT) runScan(cmd *cobra.Command, args []string) {
}
}

if err := iter.Close(); err != nil {
if err := iterCloser.Close(); err != nil {
fmt.Fprintf(stdout, "%s\n", err)
}
})
Expand Down

0 comments on commit 8471e8d

Please sign in to comment.