--- /dev/null
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package poll
+
+var TestHookDidSendFile = func(dstFD *FD, src int, written int64, err error, handled bool) {}
// SendFile wraps the sendfile system call.
func SendFile(dstFD *FD, src int, pos, remain int64) (written int64, err error, handled bool) {
+ defer func() {
+ TestHookDidSendFile(dstFD, src, written, err, handled)
+ }()
if err := dstFD.writeLock(); err != nil {
return 0, err, false
}
// SendFile wraps the sendfile system call.
func SendFile(dstFD *FD, src int, remain int64) (written int64, err error, handled bool) {
+ defer func() {
+ TestHookDidSendFile(dstFD, src, written, err, handled)
+ }()
if err := dstFD.writeLock(); err != nil {
return 0, err, false
}
// SendFile wraps the sendfile system call.
func SendFile(dstFD *FD, src int, pos, remain int64) (written int64, err error, handled bool) {
+ defer func() {
+ TestHookDidSendFile(dstFD, src, written, err, handled)
+ }()
if err := dstFD.writeLock(); err != nil {
return 0, err, false
}
// SendFile wraps the TransmitFile call.
func SendFile(fd *FD, src syscall.Handle, n int64) (written int64, err error) {
+ defer func() {
+ TestHookDidSendFile(fd, 0, written, err, written > 0)
+ }()
if fd.kind == kindPipe {
// TransmitFile does not work with pipes
return 0, syscall.ESPIPE
"os"
)
+const supportsSendfile = true
+
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
// 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
}
"encoding/hex"
"errors"
"fmt"
+ "internal/poll"
"io"
"os"
"runtime"
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()
// 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
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
return
}
- _, err = io.CopyN(conn, f, sendSize)
+ expectSendfile(t, conn, func() {
+ _, err = io.CopyN(conn, f, sendSize)
+ })
if err != nil {
errc <- err
return
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)
}
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
"syscall"
)
+const supportsSendfile = true
+
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
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
"syscall"
)
+const supportsSendfile = true
+
// sendFile copies the contents of r to c using the TransmitFile
// system call to minimize copies.
//
var (
PollCopyFileRangeP = &pollCopyFileRange
PollSpliceFile = &pollSplice
- PollSendFile = &pollSendFile
GetPollFDAndNetwork = getPollFDAndNetwork
)
func hookSendFile(t *testing.T) *sendFileHook {
h := new(sendFileHook)
- h.install()
- t.Cleanup(h.uninstall)
+ orig := poll.TestHookDidSendFile
+ t.Cleanup(func() {
+ poll.TestHookDidSendFile = orig
+ })
+ poll.TestHookDidSendFile = func(dstFD *poll.FD, src int, written int64, err error, handled bool) {
+ h.called = true
+ h.dstfd = dstFD.Sysfd
+ h.srcfd = src
+ h.written = written
+ h.err = err
+ h.handled = handled
+ }
return h
}
called bool
dstfd int
srcfd int
- remain int64
written int64
handled bool
err error
-
- original func(dst *poll.FD, src int, remain int64) (int64, error, bool)
-}
-
-func (h *sendFileHook) install() {
- h.original = *PollSendFile
- *PollSendFile = func(dst *poll.FD, src int, remain int64) (int64, error, bool) {
- h.called = true
- h.dstfd = dst.Sysfd
- h.srcfd = src
- h.remain = remain
- h.written, h.err, h.handled = h.original(dst, src, remain)
- return h.written, h.err, h.handled
- }
-}
-
-func (h *sendFileHook) uninstall() {
- *PollSendFile = h.original
}
func createTempFile(t *testing.T, size int64) (*File, []byte) {
var (
pollCopyFileRange = poll.CopyFileRange
pollSplice = poll.Splice
- pollSendFile = poll.SendFile
)
// wrapSyscallError takes an error and a syscall name. If the error is
}
rerr := sc.Read(func(fd uintptr) (done bool) {
- written, err, handled = pollSendFile(pfd, int(fd), 1<<63-1)
+ written, err, handled = poll.SendFile(pfd, int(fd), 1<<63-1)
return true
})