]> Cypherpunks repositories - gostls13.git/commitdiff
io: correctly process result of sendfile(2) when src returns 0 bytes
authorDaulet Zhanguzin <daulet@zhanguzin.kz>
Wed, 6 Jul 2022 22:29:35 +0000 (22:29 +0000)
committerGopher Robot <gobot@golang.org>
Wed, 9 Nov 2022 03:01:33 +0000 (03:01 +0000)
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: 3a50be4f169683bf9caea32892c66619a66ad21a
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>
src/internal/poll/sendfile_linux.go
src/net/sendfile_linux.go

index 6e7852347b7516057a6080a44ad2c9414289587e..cc31969a43f9f8737e31f5a110a4c7aaf32405bd 100644 (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
 }
index e5150aa5e8a3644488ed3c922c84e9c988fbb857..0299fdc3b5105ed969f258c128fc3d0257c540ee 100644 (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
 }