avoid sendFile() for ranges or object lengths < 4MiB (#20141)

This commit is contained in:
Harshavardhana 2024-07-24 03:22:50 -07:00 committed by GitHub
parent b368d4cc13
commit 6fe2b3f901
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 34 additions and 32 deletions

View File

@ -36,6 +36,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/dustin/go-humanize"
"github.com/minio/minio/internal/grid" "github.com/minio/minio/internal/grid"
"github.com/tinylib/msgp/msgp" "github.com/tinylib/msgp/msgp"
@ -594,44 +595,47 @@ func (s *storageRESTServer) ReadFileStreamHandler(w http.ResponseWriter, r *http
} }
volume := r.Form.Get(storageRESTVolume) volume := r.Form.Get(storageRESTVolume)
filePath := r.Form.Get(storageRESTFilePath) filePath := r.Form.Get(storageRESTFilePath)
offset, err := strconv.Atoi(r.Form.Get(storageRESTOffset)) offset, err := strconv.ParseInt(r.Form.Get(storageRESTOffset), 10, 64)
if err != nil { if err != nil {
s.writeErrorResponse(w, err) s.writeErrorResponse(w, err)
return return
} }
length, err := strconv.Atoi(r.Form.Get(storageRESTLength)) length, err := strconv.ParseInt(r.Form.Get(storageRESTLength), 10, 64)
if err != nil { if err != nil {
s.writeErrorResponse(w, err) s.writeErrorResponse(w, err)
return return
} }
w.Header().Set(xhttp.ContentLength, strconv.Itoa(length)) w.Header().Set(xhttp.ContentLength, strconv.FormatInt(length, 10))
rc, err := s.getStorage().ReadFileStream(r.Context(), volume, filePath, int64(offset), int64(length)) rc, err := s.getStorage().ReadFileStream(r.Context(), volume, filePath, offset, length)
if err != nil { if err != nil {
s.writeErrorResponse(w, err) s.writeErrorResponse(w, err)
return return
} }
defer rc.Close() defer rc.Close()
rf, ok := w.(io.ReaderFrom) noReadFrom := runtime.GOOS == "windows" || length < 4*humanize.MiByte
if ok && runtime.GOOS != "windows" { if !noReadFrom {
// Attempt to use splice/sendfile() optimization, A very specific behavior mentioned below is necessary. rf, ok := w.(io.ReaderFrom)
// See https://github.com/golang/go/blob/f7c5cbb82087c55aa82081e931e0142783700ce8/src/net/sendfile_linux.go#L20
// Windows can lock up with this optimization, so we fall back to regular copy.
sr, ok := rc.(*sendFileReader)
if ok { if ok {
// Sendfile sends in 4MiB chunks per sendfile syscall which is more than enough // Attempt to use splice/sendfile() optimization, A very specific behavior mentioned below is necessary.
// for most setups. // See https://github.com/golang/go/blob/f7c5cbb82087c55aa82081e931e0142783700ce8/src/net/sendfile_linux.go#L20
_, err = rf.ReadFrom(sr.Reader) // Windows can lock up with this optimization, so we fall back to regular copy.
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients sr, ok := rc.(*sendFileReader)
storageLogIf(r.Context(), err) if ok {
} // Sendfile sends in 4MiB chunks per sendfile syscall which is more than enough
if err == nil || !errors.Is(err, xhttp.ErrNotImplemented) { // for most setups.
return _, err = rf.ReadFrom(sr.Reader)
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
storageLogIf(r.Context(), err)
}
if err == nil || !errors.Is(err, xhttp.ErrNotImplemented) {
return
}
} }
} }
} // Fallback to regular copy } // noReadFrom means use io.Copy()
_, err = xioutil.Copy(w, rc) _, err = xioutil.Copy(w, rc)
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients

View File

@ -2067,7 +2067,6 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
return nil, err return nil, err
} }
} }
return &sendFileReader{Reader: io.LimitReader(file, length), Closer: file}, nil return &sendFileReader{Reader: io.LimitReader(file, length), Closer: file}, nil
} }

View File

@ -243,6 +243,15 @@ func NopCloser(w io.Writer) io.WriteCloser {
return nopCloser{w} return nopCloser{w}
} }
const copyBufferSize = 32 * 1024
var copyBufPool = sync.Pool{
New: func() interface{} {
b := make([]byte, copyBufferSize)
return &b
},
}
// SkipReader skips a given number of bytes and then returns all // SkipReader skips a given number of bytes and then returns all
// remaining data. // remaining data.
type SkipReader struct { type SkipReader struct {
@ -285,21 +294,11 @@ func NewSkipReader(r io.Reader, n int64) io.Reader {
return &SkipReader{r, n} return &SkipReader{r, n}
} }
const copyBufferSize = 32 * 1024
var copyBufPool = sync.Pool{
New: func() interface{} {
b := make([]byte, copyBufferSize)
return &b
},
}
// Copy is exactly like io.Copy but with reusable buffers. // Copy is exactly like io.Copy but with reusable buffers.
func Copy(dst io.Writer, src io.Reader) (written int64, err error) { func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
bufp := copyBufPool.Get().(*[]byte) bufp := ODirectPoolLarge.Get().(*[]byte)
buf := *bufp buf := *bufp
buf = buf[:copyBufferSize] defer ODirectPoolLarge.Put(bufp)
defer copyBufPool.Put(bufp)
return io.CopyBuffer(dst, src, buf) return io.CopyBuffer(dst, src, buf)
} }