forked from TrueCloudLab/rclone
vfs: try to seek buffer on read only files
This commit is contained in:
parent
7d35b14138
commit
552eb8e06b
4 changed files with 167 additions and 1 deletions
|
@ -96,6 +96,16 @@ func (acc *Account) GetReader() io.ReadCloser {
|
||||||
return acc.origIn
|
return acc.origIn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAsyncReader returns the current AsyncReader or nil if Account is unbuffered
|
||||||
|
func (acc *Account) GetAsyncReader() *asyncreader.AsyncReader {
|
||||||
|
acc.mu.Lock()
|
||||||
|
defer acc.mu.Unlock()
|
||||||
|
if asyncIn, ok := acc.in.(*asyncreader.AsyncReader); ok {
|
||||||
|
return asyncIn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// StopBuffering stops the async buffer doing any more buffering
|
// StopBuffering stops the async buffer doing any more buffering
|
||||||
func (acc *Account) StopBuffering() {
|
func (acc *Account) StopBuffering() {
|
||||||
if asyncIn, ok := acc.in.(*asyncreader.AsyncReader); ok {
|
if asyncIn, ok := acc.in.(*asyncreader.AsyncReader); ok {
|
||||||
|
|
|
@ -180,6 +180,76 @@ func (a *AsyncReader) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SkipBytes will try to seek 'skip' bytes relative to the current position.
|
||||||
|
// On success it returns true. If 'skip' is outside the current buffer data or
|
||||||
|
// an error occurs, Abandon is called and false is returned.
|
||||||
|
func (a *AsyncReader) SkipBytes(skip int) (ok bool) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer func() {
|
||||||
|
a.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
a.Abandon()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if a.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if skip < 0 {
|
||||||
|
// seek backwards if skip is inside current buffer
|
||||||
|
if a.cur != nil && a.cur.offset+skip >= 0 {
|
||||||
|
a.cur.offset += skip
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// early return if skip is past the maximum buffer capacity
|
||||||
|
if skip >= (len(a.ready)+1)*BufferSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
refillTokens := 0
|
||||||
|
for {
|
||||||
|
if a.cur.isEmpty() {
|
||||||
|
if a.cur != nil {
|
||||||
|
a.putBuffer(a.cur)
|
||||||
|
refillTokens++
|
||||||
|
a.cur = nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case b, ok := <-a.ready:
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a.cur = b
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(a.cur.buffer())
|
||||||
|
if n > skip {
|
||||||
|
n = skip
|
||||||
|
}
|
||||||
|
a.cur.increment(n)
|
||||||
|
skip -= n
|
||||||
|
if skip == 0 {
|
||||||
|
for ; refillTokens > 0; refillTokens-- {
|
||||||
|
a.token <- struct{}{}
|
||||||
|
}
|
||||||
|
// If at end of buffer, store any error, if present
|
||||||
|
if a.cur.isEmpty() && a.cur.err != nil {
|
||||||
|
a.err = a.cur.err
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if a.cur.err != nil {
|
||||||
|
a.err = a.cur.err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Abandon will ensure that the underlying async reader is shut down.
|
// Abandon will ensure that the underlying async reader is shut down.
|
||||||
// It will NOT close the input supplied on New.
|
// It will NOT close the input supplied on New.
|
||||||
func (a *AsyncReader) Abandon() {
|
func (a *AsyncReader) Abandon() {
|
||||||
|
|
|
@ -3,14 +3,17 @@ package asyncreader
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/iotest"
|
"testing/iotest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncw/rclone/lib/readers"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -281,3 +284,78 @@ func testAsyncReaderClose(t *testing.T, writeto bool) {
|
||||||
}
|
}
|
||||||
func TestAsyncReaderCloseRead(t *testing.T) { testAsyncReaderClose(t, false) }
|
func TestAsyncReaderCloseRead(t *testing.T) { testAsyncReaderClose(t, false) }
|
||||||
func TestAsyncReaderCloseWriteTo(t *testing.T) { testAsyncReaderClose(t, true) }
|
func TestAsyncReaderCloseWriteTo(t *testing.T) { testAsyncReaderClose(t, true) }
|
||||||
|
|
||||||
|
func TestAsyncReaderSkipBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
data := make([]byte, 15000)
|
||||||
|
buf := make([]byte, len(data))
|
||||||
|
r := rand.New(rand.NewSource(42))
|
||||||
|
|
||||||
|
n, err := r.Read(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, len(data), n)
|
||||||
|
|
||||||
|
initialReads := []int{0, 1, 100, 2048,
|
||||||
|
softStartInitial - 1, softStartInitial, softStartInitial + 1,
|
||||||
|
8000, len(data)}
|
||||||
|
skips := []int{-1000, -101, -100, -99, 0, 1, 2048,
|
||||||
|
softStartInitial - 1, softStartInitial, softStartInitial + 1,
|
||||||
|
8000, len(data), BufferSize, 2 * BufferSize}
|
||||||
|
|
||||||
|
for buffers := 1; buffers <= 5; buffers++ {
|
||||||
|
t.Run(fmt.Sprintf("%d", buffers), func(t *testing.T) {
|
||||||
|
for _, initialRead := range initialReads {
|
||||||
|
t.Run(fmt.Sprintf("%d", initialRead), func(t *testing.T) {
|
||||||
|
for _, skip := range skips {
|
||||||
|
t.Run(fmt.Sprintf("%d", skip), func(t *testing.T) {
|
||||||
|
ar, err := New(ioutil.NopCloser(bytes.NewReader(data)), buffers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wantSkipFalse := false
|
||||||
|
buf = buf[:initialRead]
|
||||||
|
n, err := readers.ReadFill(ar, buf)
|
||||||
|
if initialRead >= len(data) {
|
||||||
|
wantSkipFalse = true
|
||||||
|
if initialRead > len(data) {
|
||||||
|
assert.Equal(t, err, io.EOF)
|
||||||
|
} else {
|
||||||
|
assert.True(t, err == nil || err == io.EOF)
|
||||||
|
}
|
||||||
|
assert.Equal(t, len(data), n)
|
||||||
|
assert.Equal(t, data, buf[:len(data)])
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, initialRead, n)
|
||||||
|
assert.Equal(t, data[:initialRead], buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
skipped := ar.SkipBytes(skip)
|
||||||
|
buf = buf[:1024]
|
||||||
|
n, err = readers.ReadFill(ar, buf)
|
||||||
|
offset := initialRead + skip
|
||||||
|
if skipped {
|
||||||
|
assert.False(t, wantSkipFalse)
|
||||||
|
l := len(buf)
|
||||||
|
if offset >= len(data) {
|
||||||
|
assert.Equal(t, err, io.EOF)
|
||||||
|
} else {
|
||||||
|
if offset+1024 >= len(data) {
|
||||||
|
l = len(data) - offset
|
||||||
|
}
|
||||||
|
assert.Equal(t, l, n)
|
||||||
|
assert.Equal(t, data[offset:offset+l], buf[:l])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if initialRead >= len(data) {
|
||||||
|
assert.Equal(t, err, io.EOF)
|
||||||
|
} else {
|
||||||
|
assert.True(t, err == errorStreamAbandoned || err == io.EOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
10
vfs/read.go
10
vfs/read.go
|
@ -104,8 +104,16 @@ func (fh *ReadFileHandle) seek(offset int64, reopen bool) (err error) {
|
||||||
if fh.noSeek {
|
if fh.noSeek {
|
||||||
return ESPIPE
|
return ESPIPE
|
||||||
}
|
}
|
||||||
fh.r.StopBuffering() // stop the background reading first
|
|
||||||
fh.hash = nil
|
fh.hash = nil
|
||||||
|
if !reopen {
|
||||||
|
ar := fh.r.GetAsyncReader()
|
||||||
|
// try to fullfill the seek with buffer discard
|
||||||
|
if ar != nil && ar.SkipBytes(int(offset-fh.offset)) {
|
||||||
|
fh.offset = offset
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fh.r.StopBuffering() // stop the background reading first
|
||||||
oldReader := fh.r.GetReader()
|
oldReader := fh.r.GetReader()
|
||||||
r, ok := oldReader.(*chunkedreader.ChunkedReader)
|
r, ok := oldReader.(*chunkedreader.ChunkedReader)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Add table
Reference in a new issue