Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blob: Fix panics if reader recreation fails after Seek #3425

Merged
merged 1 commit into from Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions blob/blob.go
Expand Up @@ -128,12 +128,11 @@ func (r *Reader) Read(p []byte) (int, error) {
// to (SeekEnd, 0) and use the return value to determine the size
// of the data, then Seek back to (SeekStart, 0).
saved := r.savedOffset
r.savedOffset = -1
if r.relativeOffset == saved {
// Nope! We're at the same place we left off.
r.savedOffset = -1
} else {
// Yep! We've changed the offset. Recreate the reader.
_ = r.r.Close()
length := r.baseLength
if length >= 0 {
length -= r.relativeOffset
Expand All @@ -142,11 +141,13 @@ func (r *Reader) Read(p []byte) (int, error) {
return 0, gcerr.Newf(gcerr.Internal, nil, "blob: invalid Seek (base length %d, relative offset %d)", r.baseLength, r.relativeOffset)
}
}
var err error
r.r, err = r.b.NewRangeReader(r.ctx, r.key, r.baseOffset+r.relativeOffset, length, r.dopts)
newR, err := r.b.NewRangeReader(r.ctx, r.key, r.baseOffset+r.relativeOffset, length, r.dopts)
if err != nil {
return 0, wrapError(r.b, err, r.key)
}
_ = r.r.Close()
r.savedOffset = -1
r.r = newR
}
}
n, err := r.r.Read(p)
Expand Down
68 changes: 68 additions & 0 deletions blob/blob_test.go
Expand Up @@ -265,6 +265,74 @@ func TestDownloader(t *testing.T) {
}
}

func TestSeekAfterReadFailure(t *testing.T) {
const filename = "f.txt"

ctx := context.Background()

bucket := NewBucket(&oneTimeReadBucket{first: true})
defer bucket.Close()

reader, err := bucket.NewRangeReader(ctx, filename, 0, 100, nil)
if err != nil {
t.Fatalf("failed NewRangeReader: %v", err)
}
defer reader.Close()

b := make([]byte, 10)

_, err = reader.Read(b)
if err != nil {
t.Fatalf("failed Read#1: %v", err)
}

_, err = reader.Seek(0, io.SeekStart)
if err != nil {
t.Fatalf("failed Seek#1: %v", err)
}

// This Read will force a recreation of the reader via NewRangeReader,
// which will fail.
_, err = reader.Read(b)
if err == nil {
t.Fatalf("unexpectedly succeeded Read#2: %v", err)
}

_, err = reader.Seek(0, io.SeekStart)
if err != nil {
t.Fatalf("failed Seek#2: %v", err)
}
}

// oneTimeReadBucket implements driver.Bucket for TestSeekAfterReadFailure.
// It returns a fake reader that succeeds once, then fails.
type oneTimeReadBucket struct {
driver.Bucket
first bool
}

type workingReader struct {
driver.Reader
}

func (r *workingReader) Read(p []byte) (int, error) {
return len(p), nil
}

func (r *workingReader) Attributes() *driver.ReaderAttributes { return &driver.ReaderAttributes{} }
func (r *workingReader) Close() error { return nil }

func (b *oneTimeReadBucket) NewRangeReader(ctx context.Context, key string, offset, length int64, opts *driver.ReaderOptions) (driver.Reader, error) {
if b.first {
b.first = false
return &workingReader{}, nil
}
return nil, errFake
}

func (b *oneTimeReadBucket) ErrorCode(err error) gcerrors.ErrorCode { return gcerrors.Unknown }
func (b *oneTimeReadBucket) Close() error { return nil }

// erroringBucket implements driver.Bucket. All interface methods that return
// errors are implemented, and return errFake.
// In addition, when passed the key "work", NewRangeReader and NewTypedWriter
Expand Down