mirror of
https://github.com/restic/restic.git
synced 2026-08-25 15:03:17 +00:00
Reworked Backend.Load API to retry errors during ongoing download
Signed-off-by: Igor Fedorenko <igor@ifedorenko.com>
This commit is contained in:
@@ -178,10 +178,13 @@ func (wr wrapReader) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
}
|
||||
|
||||
func (be *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h))
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -142,9 +142,13 @@ func (be *b2Backend) IsNotExist(err error) bool {
|
||||
return b2.IsNotExist(errors.Cause(err))
|
||||
}
|
||||
|
||||
// Load returns the data stored in the backend for h at the given offset
|
||||
// and saves it in p. Load has the same semantics as io.ReaderAt.
|
||||
func (be *b2Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *b2Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
}
|
||||
|
||||
func (be *b2Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h))
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -66,12 +66,12 @@ func (be *ErrorBackend) Save(ctx context.Context, h restic.Handle, rd io.Reader)
|
||||
// given offset. If length is larger than zero, only a portion of the file
|
||||
// is returned. rd must be closed after use. If an error is returned, the
|
||||
// ReadCloser must be nil.
|
||||
func (be *ErrorBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
func (be *ErrorBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
|
||||
if be.fail(be.FailLoad) {
|
||||
return nil, errors.Errorf("Load(%v, %v, %v) random error induced", h, length, offset)
|
||||
return errors.Errorf("Load(%v, %v, %v) random error induced", h, length, offset)
|
||||
}
|
||||
|
||||
return be.Backend.Load(ctx, h, length, offset)
|
||||
return be.Backend.Load(ctx, h, length, offset, consumer)
|
||||
}
|
||||
|
||||
// Stat returns information about the File identified by h.
|
||||
|
||||
@@ -88,15 +88,11 @@ func (be *RetryBackend) Save(ctx context.Context, h restic.Handle, rd io.Reader)
|
||||
// given offset. If length is larger than zero, only a portion of the file
|
||||
// is returned. rd must be closed after use. If an error is returned, the
|
||||
// ReadCloser must be nil.
|
||||
func (be *RetryBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (rd io.ReadCloser, err error) {
|
||||
err = be.retry(ctx, fmt.Sprintf("Load(%v, %v, %v)", h, length, offset),
|
||||
func (be *RetryBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) (err error) {
|
||||
return be.retry(ctx, fmt.Sprintf("Load(%v, %v, %v)", h, length, offset),
|
||||
func() error {
|
||||
var innerError error
|
||||
rd, innerError = be.Backend.Load(ctx, h, length, offset)
|
||||
|
||||
return innerError
|
||||
return be.Backend.Load(ctx, h, length, offset, consumer)
|
||||
})
|
||||
return rd, err
|
||||
}
|
||||
|
||||
// Stat returns information about the File identified by h.
|
||||
|
||||
@@ -123,3 +123,66 @@ func TestBackendListRetry(t *testing.T) {
|
||||
test.Equals(t, 2, retry) // assert retried once
|
||||
test.Equals(t, []string{ID1, ID2}, listed) // assert no duplicate files
|
||||
}
|
||||
|
||||
// failingReader returns an error after reading limit number of bytes
|
||||
type failingReader struct {
|
||||
data []byte
|
||||
pos int
|
||||
limit int
|
||||
}
|
||||
|
||||
func (r failingReader) Read(p []byte) (n int, err error) {
|
||||
i := 0
|
||||
for ; i < len(p) && i+r.pos < r.limit; i++ {
|
||||
p[i] = r.data[r.pos+i]
|
||||
}
|
||||
r.pos += i
|
||||
if r.pos >= r.limit {
|
||||
return i, errors.Errorf("reader reached limit of %d", r.limit)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
func (r failingReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// closingReader adapts io.Reader to io.ReadCloser interface
|
||||
type closingReader struct {
|
||||
rd io.Reader
|
||||
}
|
||||
|
||||
func (r closingReader) Read(p []byte) (n int, err error) {
|
||||
return r.rd.Read(p)
|
||||
}
|
||||
func (r closingReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBackendLoadRetry(t *testing.T) {
|
||||
data := test.Random(23, 1024)
|
||||
limit := 100
|
||||
attempt := 0
|
||||
|
||||
be := mock.NewBackend()
|
||||
be.OpenReaderFn = func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// returns failing reader on first invocation, good reader on subsequent invocations
|
||||
attempt++
|
||||
if attempt > 1 {
|
||||
return closingReader{rd: bytes.NewReader(data)}, nil
|
||||
}
|
||||
return failingReader{data: data, limit: limit}, nil
|
||||
}
|
||||
|
||||
retryBackend := RetryBackend{
|
||||
Backend: be,
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
err := retryBackend.Load(context.TODO(), restic.Handle{}, 0, 0, func(rd io.Reader) (err error) {
|
||||
buf, err = ioutil.ReadAll(rd)
|
||||
return err
|
||||
})
|
||||
test.OK(t, err)
|
||||
test.Equals(t, data, buf)
|
||||
test.Equals(t, 2, attempt)
|
||||
}
|
||||
|
||||
@@ -282,10 +282,13 @@ func (wr wrapReader) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
}
|
||||
|
||||
func (be *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h))
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -146,10 +146,13 @@ func (b *Local) Save(ctx context.Context, h restic.Handle, rd io.Reader) error {
|
||||
return setNewFileMode(filename, backend.Modes.File)
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (b *Local) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (b *Local) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, b.openReader, fn)
|
||||
}
|
||||
|
||||
func (b *Local) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v", h, length, offset)
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
@@ -85,10 +86,13 @@ func (be *MemoryBackend) Save(ctx context.Context, h restic.Handle, rd io.Reader
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (be *MemoryBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *MemoryBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
}
|
||||
|
||||
func (be *MemoryBackend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -166,10 +166,13 @@ func (b *restBackend) IsNotExist(err error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (b *restBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (b *restBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, b.openReader, fn)
|
||||
}
|
||||
|
||||
func (b *restBackend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v", h, length, offset)
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -281,10 +281,13 @@ func (wr wrapReader) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
}
|
||||
|
||||
func (be *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v from %v", h, length, offset, be.Filename(h))
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -327,10 +327,13 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err err
|
||||
return errors.Wrap(r.c.Chmod(filename, backend.Modes.File), "Chmod")
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (r *SFTP) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (r *SFTP) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, r.openReader, fn)
|
||||
}
|
||||
|
||||
func (r *SFTP) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v", h, length, offset)
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -109,10 +109,13 @@ func (be *beSwift) Location() string {
|
||||
return be.container
|
||||
}
|
||||
|
||||
// Load returns a reader that yields the contents of the file at h at the
|
||||
// given offset. If length is nonzero, only a portion of the file is
|
||||
// returned. rd must be closed after use.
|
||||
func (be *beSwift) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||
// given offset.
|
||||
func (be *beSwift) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return backend.DefaultLoad(ctx, h, length, offset, be.openReader, fn)
|
||||
}
|
||||
|
||||
func (be *beSwift) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
debug.Log("Load %v, length %v, offset %v", h, length, offset)
|
||||
if err := h.Valid(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -42,20 +42,15 @@ func (s *Suite) BenchmarkLoadFile(t *testing.B) {
|
||||
t.ResetTimer()
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
rd, err := be.Load(context.TODO(), handle, 0, 0)
|
||||
var n int
|
||||
err := be.Load(context.TODO(), handle, 0, 0, func(rd io.Reader) (ierr error) {
|
||||
n, ierr = io.ReadFull(rd, buf)
|
||||
return ierr
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(rd, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Fatalf("Close() returned error: %v", err)
|
||||
}
|
||||
|
||||
if n != length {
|
||||
t.Fatalf("wrong number of bytes read: want %v, got %v", length, n)
|
||||
}
|
||||
@@ -84,20 +79,15 @@ func (s *Suite) BenchmarkLoadPartialFile(t *testing.B) {
|
||||
t.ResetTimer()
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
rd, err := be.Load(context.TODO(), handle, testLength, 0)
|
||||
var n int
|
||||
err := be.Load(context.TODO(), handle, testLength, 0, func(rd io.Reader) (ierr error) {
|
||||
n, ierr = io.ReadFull(rd, buf)
|
||||
return ierr
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(rd, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Fatalf("Close() returned error: %v", err)
|
||||
}
|
||||
|
||||
if n != testLength {
|
||||
t.Fatalf("wrong number of bytes read: want %v, got %v", testLength, n)
|
||||
}
|
||||
@@ -128,20 +118,15 @@ func (s *Suite) BenchmarkLoadPartialFileOffset(t *testing.B) {
|
||||
t.ResetTimer()
|
||||
|
||||
for i := 0; i < t.N; i++ {
|
||||
rd, err := be.Load(context.TODO(), handle, testLength, int64(testOffset))
|
||||
var n int
|
||||
err := be.Load(context.TODO(), handle, testLength, int64(testOffset), func(rd io.Reader) (ierr error) {
|
||||
n, ierr = io.ReadFull(rd, buf)
|
||||
return ierr
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(rd, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Fatalf("Close() returned error: %v", err)
|
||||
}
|
||||
|
||||
if n != testLength {
|
||||
t.Fatalf("wrong number of bytes read: want %v, got %v", testLength, n)
|
||||
}
|
||||
|
||||
@@ -115,13 +115,14 @@ func (s *Suite) TestLoad(t *testing.T) {
|
||||
b := s.open(t)
|
||||
defer s.close(t, b)
|
||||
|
||||
rd, err := b.Load(context.TODO(), restic.Handle{}, 0, 0)
|
||||
noop := func(rd io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := b.Load(context.TODO(), restic.Handle{}, 0, 0, noop)
|
||||
if err == nil {
|
||||
t.Fatalf("Load() did not return an error for invalid handle")
|
||||
}
|
||||
if rd != nil {
|
||||
_ = rd.Close()
|
||||
}
|
||||
|
||||
err = testLoad(b, restic.Handle{Type: restic.DataFile, Name: "foobar"}, 0, 0)
|
||||
if err == nil {
|
||||
@@ -141,13 +142,19 @@ func (s *Suite) TestLoad(t *testing.T) {
|
||||
|
||||
t.Logf("saved %d bytes as %v", length, handle)
|
||||
|
||||
rd, err = b.Load(context.TODO(), handle, 100, -1)
|
||||
err = b.Load(context.TODO(), handle, 100, -1, noop)
|
||||
if err == nil {
|
||||
t.Fatalf("Load() returned no error for negative offset!")
|
||||
}
|
||||
|
||||
if rd != nil {
|
||||
t.Fatalf("Load() returned a non-nil reader for negative offset!")
|
||||
err = b.Load(context.TODO(), handle, 0, 0, func(rd io.Reader) error {
|
||||
return errors.Errorf("deliberate error")
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("Load() did not propagate consumer error!")
|
||||
}
|
||||
if err.Error() != "deliberate error" {
|
||||
t.Fatalf("Load() did not correctly propagate consumer error!")
|
||||
}
|
||||
|
||||
loadTests := 50
|
||||
@@ -176,63 +183,38 @@ func (s *Suite) TestLoad(t *testing.T) {
|
||||
d = d[:l]
|
||||
}
|
||||
|
||||
rd, err := b.Load(context.TODO(), handle, getlen, int64(o))
|
||||
var buf []byte
|
||||
err := b.Load(context.TODO(), handle, getlen, int64(o), func(rd io.Reader) (ierr error) {
|
||||
buf, ierr = ioutil.ReadAll(rd)
|
||||
return ierr
|
||||
})
|
||||
if err != nil {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) returned unexpected error: %+v", l, o, err)
|
||||
continue
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(rd)
|
||||
if err != nil {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) ReadAll() returned unexpected error: %+v", l, o, err)
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if l == 0 && len(buf) != len(d) {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) wrong number of bytes read: want %d, got %d", l, o, len(d), len(buf))
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if l > 0 && l <= len(d) && len(buf) != l {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) wrong number of bytes read: want %d, got %d", l, o, l, len(buf))
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if l > len(d) && len(buf) != len(d) {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) wrong number of bytes read for overlong read: want %d, got %d", l, o, l, len(buf))
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf, d) {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) returned wrong bytes", l, o)
|
||||
if err = rd.Close(); err != nil {
|
||||
t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = rd.Close()
|
||||
if err != nil {
|
||||
t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen)
|
||||
t.Errorf("Load(%d, %d) rd.Close() returned unexpected error: %+v", l, o, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -647,17 +629,10 @@ func store(t testing.TB, b restic.Backend, tpe restic.FileType, data []byte) res
|
||||
|
||||
// testLoad loads a blob (but discards its contents).
|
||||
func testLoad(b restic.Backend, h restic.Handle, length int, offset int64) error {
|
||||
rd, err := b.Load(context.TODO(), h, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(ioutil.Discard, rd)
|
||||
cerr := rd.Close()
|
||||
if err == nil {
|
||||
err = cerr
|
||||
}
|
||||
return err
|
||||
return b.Load(context.TODO(), h, 0, 0, func(rd io.Reader) (ierr error) {
|
||||
_, ierr = io.Copy(ioutil.Discard, rd)
|
||||
return ierr
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Suite) delayedRemove(t testing.TB, be restic.Backend, handles ...restic.Handle) error {
|
||||
@@ -776,18 +751,14 @@ func (s *Suite) TestBackend(t *testing.T) {
|
||||
length := end - start
|
||||
|
||||
buf2 := make([]byte, length)
|
||||
rd, err := b.Load(context.TODO(), h, len(buf2), int64(start))
|
||||
var n int
|
||||
err = b.Load(context.TODO(), h, len(buf2), int64(start), func(rd io.Reader) (ierr error) {
|
||||
n, ierr = io.ReadFull(rd, buf2)
|
||||
return ierr
|
||||
})
|
||||
test.OK(t, err)
|
||||
n, err := io.ReadFull(rd, buf2)
|
||||
test.OK(t, err)
|
||||
test.Equals(t, len(buf2), n)
|
||||
|
||||
remaining, err := io.Copy(ioutil.Discard, rd)
|
||||
test.OK(t, err)
|
||||
test.Equals(t, int64(0), remaining)
|
||||
|
||||
test.OK(t, rd.Close())
|
||||
|
||||
test.Equals(t, ts.data[start:end], string(buf2))
|
||||
}
|
||||
|
||||
|
||||
+21
-18
@@ -10,24 +10,11 @@ import (
|
||||
|
||||
// LoadAll reads all data stored in the backend for the handle.
|
||||
func LoadAll(ctx context.Context, be restic.Backend, h restic.Handle) (buf []byte, err error) {
|
||||
rd, err := be.Load(ctx, h, 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_, e := io.Copy(ioutil.Discard, rd)
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
|
||||
e = rd.Close()
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
return ioutil.ReadAll(rd)
|
||||
err = be.Load(ctx, h, 0, 0, func(rd io.Reader) (ierr error) {
|
||||
buf, ierr = ioutil.ReadAll(rd)
|
||||
return ierr
|
||||
})
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// LimitedReadCloser wraps io.LimitedReader and exposes the Close() method.
|
||||
@@ -46,3 +33,19 @@ func (l *LimitedReadCloser) Read(p []byte) (int, error) {
|
||||
func LimitReadCloser(r io.ReadCloser, n int64) *LimitedReadCloser {
|
||||
return &LimitedReadCloser{ReadCloser: r, Reader: io.LimitReader(r, n)}
|
||||
}
|
||||
|
||||
// DefaultLoad implements Backend.Load using lower-level openReader func
|
||||
func DefaultLoad(ctx context.Context, h restic.Handle, length int, offset int64,
|
||||
openReader func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error),
|
||||
fn func(rd io.Reader) error) error {
|
||||
rd, err := openReader(ctx, h, length, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fn(rd)
|
||||
if err != nil {
|
||||
rd.Close() // ignore secondary errors closing the reader
|
||||
return err
|
||||
}
|
||||
return rd.Close()
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package backend_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/backend/mem"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
@@ -89,3 +91,54 @@ func TestLoadLargeBuffer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockReader struct {
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (rd *mockReader) Read(p []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (rd *mockReader) Close() error {
|
||||
rd.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDefaultLoad(t *testing.T) {
|
||||
|
||||
h := restic.Handle{Name: "id", Type: restic.DataFile}
|
||||
rd := &mockReader{}
|
||||
|
||||
// happy case, assert correct parameters are passed around and content stream is closed
|
||||
err := backend.DefaultLoad(context.TODO(), h, 10, 11, func(ctx context.Context, ih restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
rtest.Equals(t, h, ih)
|
||||
rtest.Equals(t, int(10), length)
|
||||
rtest.Equals(t, int64(11), offset)
|
||||
|
||||
return rd, nil
|
||||
}, func(ird io.Reader) error {
|
||||
rtest.Equals(t, rd, ird)
|
||||
return nil
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, true, rd.closed)
|
||||
|
||||
// unhappy case, assert producer errors are handled correctly
|
||||
err = backend.DefaultLoad(context.TODO(), h, 10, 11, func(ctx context.Context, ih restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
return nil, errors.Errorf("producer error")
|
||||
}, func(ird io.Reader) error {
|
||||
t.Fatalf("unexpected consumer invocation")
|
||||
return nil
|
||||
})
|
||||
rtest.Equals(t, "producer error", err.Error())
|
||||
|
||||
// unhappy case, assert consumer errors are handled correctly
|
||||
rd = &mockReader{}
|
||||
err = backend.DefaultLoad(context.TODO(), h, 10, 11, func(ctx context.Context, ih restic.Handle, length int, offset int64) (io.ReadCloser, error) {
|
||||
return rd, nil
|
||||
}, func(ird io.Reader) error {
|
||||
return errors.Errorf("consumer error")
|
||||
})
|
||||
rtest.Equals(t, true, rd.closed)
|
||||
rtest.Equals(t, "consumer error", err.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user