1
0
mirror of https://github.com/golang/go synced 2024-11-23 20:50:04 -07:00

io: correctly process result of sendfile(2) when src returns 0 bytes

Fixes #53658. io.Copy() uses sendfile(2) to avoid allocating extra buffers when src is a file and dst is a TCPConn. However if src returns no bytes current logic treats it as failure and falls back to copying via user space. The following is a benchmark that illustrates the bug.

Benchmark: https://go.dev/play/p/zgZwpjUatSq

Before:
BenchmarkCopy-16          541006              2137 ns/op            4077 B/op          0 allocs/op

After:
BenchmarkCopy-16          490383              2365 ns/op             174 B/op          8 allocs/op

Change-Id: I703376d53b20e080c6204a73c96867cce16b24cf
GitHub-Last-Rev: 3a50be4f16
GitHub-Pull-Request: golang/go#53659
Reviewed-on: https://go-review.googlesource.com/c/go/+/415834
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Daulet Zhanguzin 2022-07-06 22:29:35 +00:00 committed by Gopher Robot
parent f13849a7af
commit 27c3814275
2 changed files with 14 additions and 10 deletions

View File

@ -11,18 +11,21 @@ import "syscall"
const maxSendfileSize int = 4 << 20
// SendFile wraps the sendfile system call.
func SendFile(dstFD *FD, src int, remain int64) (int64, error) {
func SendFile(dstFD *FD, src int, remain int64) (int64, error, bool) {
if err := dstFD.writeLock(); err != nil {
return 0, err
return 0, err, false
}
defer dstFD.writeUnlock()
if err := dstFD.pd.prepareWrite(dstFD.isFile); err != nil {
return 0, err
return 0, err, false
}
dst := dstFD.Sysfd
var written int64
var err error
var (
written int64
err error
handled = true
)
for remain > 0 {
n := maxSendfileSize
if int64(n) > remain {
@ -48,8 +51,9 @@ func SendFile(dstFD *FD, src int, remain int64) (int64, error) {
// support) and syscall.EINVAL (fd types which
// don't implement sendfile)
err = err1
handled = false
break
}
}
return written, err
return written, err, handled
}

View File

@ -13,8 +13,8 @@ import (
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
// if handled == true, sendFile returns the number of bytes copied and any
// non-EOF error.
// if handled == true, sendFile returns the number (potentially zero) of bytes
// copied and any non-EOF error.
//
// if handled == false, sendFile performed no work.
func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
@ -39,7 +39,7 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
var werr error
err = sc.Read(func(fd uintptr) bool {
written, werr = poll.SendFile(&c.pfd, int(fd), remain)
written, werr, handled = poll.SendFile(&c.pfd, int(fd), remain)
return true
})
if err == nil {
@ -49,5 +49,5 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
if lr != nil {
lr.N = remain - written
}
return written, wrapSyscallError("sendfile", err), written > 0
return written, wrapSyscallError("sendfile", err), handled
}