diff --git a/internal/backend/rest/rest.go b/internal/backend/rest/rest.go index 4dc271f38..a294cb35f 100644 --- a/internal/backend/rest/rest.go +++ b/internal/backend/rest/rest.go @@ -173,7 +173,32 @@ func (b *Backend) IsNotExist(err error) bool { // Load runs fn with a reader that yields the contents of the file at h at the // given offset. func (b *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, b.openReader, fn) + r, err := b.openReader(ctx, h, length, offset) + if err != nil { + return err + } + err = fn(r) + if err != nil { + _ = r.Close() // ignore error here + return err + } + + // Note: readerat.ReadAt() (the fn) uses io.ReadFull() that doesn't + // wait for EOF after reading body. Due to HTTP/2 stream multiplexing + // and goroutine timings the EOF frame arrives from server (eg. rclone) + // with a delay after reading body. Immediate close might trigger + // HTTP/2 stream reset resulting in the *stream closed* error on server, + // so we wait for EOF before closing body. + var buf [1]byte + _, err = r.Read(buf[:]) + if err == io.EOF { + err = nil + } + + if e := r.Close(); err == nil { + err = e + } + return err } func (b *Backend) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) {