aboutsummaryrefslogtreecommitdiff
path: root/src/net
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2024-04-25 11:53:45 -0700
committerDamien Neil <dneil@google.com>2024-04-26 18:12:56 +0000
commitb384ee7cebe1b386e324dcca2c93beb96ea31c9e (patch)
tree16913efe13aedcfc2c2ae37433241c6296865361 /src/net
parent196916299da7568a2a2165246e5164637df03fb9 (diff)
downloadgo-b384ee7cebe1b386e324dcca2c93beb96ea31c9e.tar.gz
go-b384ee7cebe1b386e324dcca2c93beb96ea31c9e.zip
net, os, internal/poll: test for use of sendfile
The net package's sendfile tests exercise various paths where we expect sendfile to be used, but don't verify that sendfile was in fact used. Add a hook to internal/poll.SendFile to let us verify that sendfile was called when expected. Update os package tests (which use their own hook mechanism) to use this hook as well. For #66988 Change-Id: I7afb130dcfe0063d60c6ea0f8560cf8665ad5a81 Reviewed-on: https://go-review.googlesource.com/c/go/+/581778 Reviewed-by: Ian Lance Taylor <iant@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/net')
-rw-r--r--src/net/sendfile_linux.go2
-rw-r--r--src/net/sendfile_stub.go4
-rw-r--r--src/net/sendfile_test.go71
-rw-r--r--src/net/sendfile_unix_alt.go4
-rw-r--r--src/net/sendfile_windows.go2
5 files changed, 79 insertions, 4 deletions
diff --git a/src/net/sendfile_linux.go b/src/net/sendfile_linux.go
index 9a7d005803..f8a7bec8d3 100644
--- a/src/net/sendfile_linux.go
+++ b/src/net/sendfile_linux.go
@@ -10,6 +10,8 @@ import (
"os"
)
+const supportsSendfile = true
+
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
diff --git a/src/net/sendfile_stub.go b/src/net/sendfile_stub.go
index a4fdd99ffe..7f31cc63e1 100644
--- a/src/net/sendfile_stub.go
+++ b/src/net/sendfile_stub.go
@@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build aix || js || netbsd || openbsd || ios || wasip1
+//go:build !(linux || (darwin && !ios) || dragonfly || freebsd || solaris || windows)
package net
import "io"
+const supportsSendfile = false
+
func sendFile(c *netFD, r io.Reader) (n int64, err error, handled bool) {
return 0, nil, false
}
diff --git a/src/net/sendfile_test.go b/src/net/sendfile_test.go
index 8fadb47c15..4f3411565b 100644
--- a/src/net/sendfile_test.go
+++ b/src/net/sendfile_test.go
@@ -11,6 +11,7 @@ import (
"encoding/hex"
"errors"
"fmt"
+ "internal/poll"
"io"
"os"
"runtime"
@@ -26,6 +27,48 @@ const (
newtonSHA256 = "d4a9ac22462b35e7821a4f2706c211093da678620a8f9997989ee7cf8d507bbd"
)
+// expectSendfile runs f, and verifies that internal/poll.SendFile successfully handles
+// a write to wantConn during f's execution.
+//
+// On platforms where supportsSendfile is false, expectSendfile runs f but does not
+// expect a call to SendFile.
+func expectSendfile(t *testing.T, wantConn Conn, f func()) {
+ t.Helper()
+ if !supportsSendfile {
+ f()
+ return
+ }
+ orig := poll.TestHookDidSendFile
+ defer func() {
+ poll.TestHookDidSendFile = orig
+ }()
+ var (
+ called bool
+ gotHandled bool
+ gotFD *poll.FD
+ )
+ poll.TestHookDidSendFile = func(dstFD *poll.FD, src int, written int64, err error, handled bool) {
+ if called {
+ t.Error("internal/poll.SendFile called multiple times, want one call")
+ }
+ called = true
+ gotHandled = handled
+ gotFD = dstFD
+ }
+ f()
+ if !called {
+ t.Error("internal/poll.SendFile was not called, want it to be")
+ return
+ }
+ if !gotHandled {
+ t.Error("internal/poll.SendFile did not handle the write, want it to")
+ return
+ }
+ if &wantConn.(*TCPConn).fd.pfd != gotFD {
+ t.Error("internal.poll.SendFile called with unexpected FD")
+ }
+}
+
func TestSendfile(t *testing.T) {
ln := newLocalListener(t, "tcp")
defer ln.Close()
@@ -53,7 +96,17 @@ func TestSendfile(t *testing.T) {
// Return file data using io.Copy, which should use
// sendFile if available.
- sbytes, err := io.Copy(conn, f)
+ var sbytes int64
+ switch runtime.GOOS {
+ case "windows":
+ // Windows is not using sendfile for some reason:
+ // https://go.dev/issue/67042
+ sbytes, err = io.Copy(conn, f)
+ default:
+ expectSendfile(t, conn, func() {
+ sbytes, err = io.Copy(conn, f)
+ })
+ }
if err != nil {
errc <- err
return
@@ -121,7 +174,9 @@ func TestSendfileParts(t *testing.T) {
for i := 0; i < 3; i++ {
// Return file data using io.CopyN, which should use
// sendFile if available.
- _, err = io.CopyN(conn, f, 3)
+ expectSendfile(t, conn, func() {
+ _, err = io.CopyN(conn, f, 3)
+ })
if err != nil {
errc <- err
return
@@ -180,7 +235,9 @@ func TestSendfileSeeked(t *testing.T) {
return
}
- _, err = io.CopyN(conn, f, sendSize)
+ expectSendfile(t, conn, func() {
+ _, err = io.CopyN(conn, f, sendSize)
+ })
if err != nil {
errc <- err
return
@@ -240,6 +297,10 @@ func TestSendfilePipe(t *testing.T) {
return
}
defer conn.Close()
+ // The comment above states that this should call into sendfile,
+ // but empirically it doesn't seem to do so at this time.
+ // If it does, or does on some platforms, this CopyN should be wrapped
+ // in expectSendfile.
_, err = io.CopyN(conn, r, 1)
if err != nil {
t.Error(err)
@@ -333,6 +394,10 @@ func TestSendfileOnWriteTimeoutExceeded(t *testing.T) {
}
defer f.Close()
+ // We expect this to use sendfile, but as of the time this comment was written
+ // poll.SendFile on an FD past its timeout can return an error indicating that
+ // it didn't handle the operation, resulting in a non-sendfile retry.
+ // So don't use expectSendfile here.
_, err = io.Copy(conn, f)
if errors.Is(err, os.ErrDeadlineExceeded) {
return nil
diff --git a/src/net/sendfile_unix_alt.go b/src/net/sendfile_unix_alt.go
index 5a10540f8a..9e46c4e607 100644
--- a/src/net/sendfile_unix_alt.go
+++ b/src/net/sendfile_unix_alt.go
@@ -13,6 +13,8 @@ import (
"syscall"
)
+const supportsSendfile = true
+
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
@@ -35,6 +37,8 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
return 0, nil, true
}
}
+ // r might be an *os.File or an os.fileWithoutWriteTo.
+ // Type assert to an interface rather than *os.File directly to handle the latter case.
f, ok := r.(interface {
fs.File
io.Seeker
diff --git a/src/net/sendfile_windows.go b/src/net/sendfile_windows.go
index 59b1b0d5c1..0377a485da 100644
--- a/src/net/sendfile_windows.go
+++ b/src/net/sendfile_windows.go
@@ -11,6 +11,8 @@ import (
"syscall"
)
+const supportsSendfile = true
+
// sendFile copies the contents of r to c using the TransmitFile
// system call to minimize copies.
//