net: deduplicate sendfile files

The sendfile implementation for platforms supporting it is now in
net/sendfile.go, rather than being duplicated in separate files for
each platform.

The only difference between the implementations was the poll.SendFile
parameters, which have been harmonized, and also linux strictly
asserting for os.File, which now have been relaxed to allow any
type implementing syscall.Conn.

Change-Id: Ia1a2d5ee7380710a36fc555dbf681f7e996ea2ec
Reviewed-on: https://go-review.googlesource.com/c/go/+/664075
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Quim Muntal <quimmuntal@gmail.com>
This commit is contained in:
qmuntal 2025-04-09 12:07:03 +02:00 committed by Gopher Robot
parent 9aad012a6e
commit 2c35900fe4
11 changed files with 23 additions and 138 deletions

View File

@ -4,4 +4,4 @@
package poll
var TestHookDidSendFile = func(dstFD *FD, src int, written int64, err error, handled bool) {}
var TestHookDidSendFile = func(dstFD *FD, src uintptr, written int64, err error, handled bool) {}

View File

@ -27,29 +27,29 @@ import (
// If handled is false, sendfile was unable to perform the copy,
// has not modified the source or destination,
// and the caller should perform the copy using a fallback implementation.
func SendFile(dstFD *FD, src int, size int64) (n int64, err error, handled bool) {
func SendFile(dstFD *FD, src uintptr, size int64) (n int64, err error, handled bool) {
if goos := runtime.GOOS; goos == "linux" || goos == "android" {
// Linux's sendfile doesn't require any setup:
// It sends from the current position of the source file and
// updates the position of the source after sending.
return sendFile(dstFD, src, nil, size)
return sendFile(dstFD, int(src), nil, size)
}
// Non-Linux sendfile implementations don't use the current position of the source file,
// so we need to look up the position, pass it explicitly, and adjust it after
// sendfile returns.
start, err := ignoringEINTR2(func() (int64, error) {
return syscall.Seek(src, 0, io.SeekCurrent)
return syscall.Seek(int(src), 0, io.SeekCurrent)
})
if err != nil {
return 0, err, false
}
pos := start
n, err, handled = sendFile(dstFD, src, &pos, size)
n, err, handled = sendFile(dstFD, int(src), &pos, size)
if n > 0 {
ignoringEINTR2(func() (int64, error) {
return syscall.Seek(src, start+n, io.SeekStart)
return syscall.Seek(int(src), start+n, io.SeekStart)
})
}
return n, err, handled
@ -58,7 +58,7 @@ func SendFile(dstFD *FD, src int, size int64) (n int64, err error, handled bool)
// sendFile wraps the sendfile system call.
func sendFile(dstFD *FD, src int, offset *int64, size int64) (written int64, err error, handled bool) {
defer func() {
TestHookDidSendFile(dstFD, src, written, err, handled)
TestHookDidSendFile(dstFD, uintptr(src), written, err, handled)
}()
if err := dstFD.writeLock(); err != nil {
return 0, err, false

View File

@ -10,7 +10,7 @@ import (
)
// SendFile wraps the TransmitFile call.
func SendFile(fd *FD, src syscall.Handle, size int64) (written int64, err error, handled bool) {
func SendFile(fd *FD, src uintptr, size int64) (written int64, err error, handled bool) {
defer func() {
TestHookDidSendFile(fd, 0, written, err, written > 0)
}()
@ -18,7 +18,8 @@ func SendFile(fd *FD, src syscall.Handle, size int64) (written int64, err error,
// TransmitFile does not work with pipes
return 0, syscall.ESPIPE, false
}
if ft, _ := syscall.GetFileType(src); ft == syscall.FILE_TYPE_PIPE {
hsrc := syscall.Handle(src)
if ft, _ := syscall.GetFileType(hsrc); ft == syscall.FILE_TYPE_PIPE {
return 0, syscall.ESPIPE, false
}
@ -29,11 +30,11 @@ func SendFile(fd *FD, src syscall.Handle, size int64) (written int64, err error,
// Get the file size so we don't read past the end of the file.
var fi syscall.ByHandleFileInformation
if err := syscall.GetFileInformationByHandle(src, &fi); err != nil {
if err := syscall.GetFileInformationByHandle(hsrc, &fi); err != nil {
return 0, err, false
}
fileSize := int64(fi.FileSizeHigh)<<32 + int64(fi.FileSizeLow)
startpos, err := syscall.Seek(src, 0, io.SeekCurrent)
startpos, err := syscall.Seek(hsrc, 0, io.SeekCurrent)
if err != nil {
return 0, err, false
}
@ -49,7 +50,7 @@ func SendFile(fd *FD, src syscall.Handle, size int64) (written int64, err error,
// Some versions of Windows (Windows 10 1803) do not set
// file position after TransmitFile completes.
// So just use Seek to set file position.
_, serr := syscall.Seek(src, startpos+written, io.SeekStart)
_, serr := syscall.Seek(hsrc, startpos+written, io.SeekStart)
if err != nil {
err = serr
}
@ -62,7 +63,7 @@ func SendFile(fd *FD, src syscall.Handle, size int64) (written int64, err error,
const maxChunkSizePerCall = int64(0x7fffffff - 1)
o := &fd.wop
o.handle = src
o.handle = hsrc
for size > 0 {
chunkSize := maxChunkSizePerCall
if chunkSize > size {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (darwin && !ios) || dragonfly || freebsd || solaris
//go:build linux || (darwin && !ios) || dragonfly || freebsd || solaris || windows
package net
@ -44,7 +44,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, handled = poll.SendFile(&c.pfd, int(fd), remain)
written, werr, handled = poll.SendFile(&c.pfd, fd, remain)
return true
})
if err == nil {

View File

@ -1,55 +0,0 @@
// Copyright 2011 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 net
import (
"internal/poll"
"io"
"os"
)
const supportsSendfile = true
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
// 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) {
var remain int64 = 0 // 0 indicates sending until EOF
lr, ok := r.(*io.LimitedReader)
if ok {
remain, r = lr.N, lr.R
if remain <= 0 {
return 0, nil, true
}
}
f, ok := r.(*os.File)
if !ok {
return 0, nil, false
}
sc, err := f.SyscallConn()
if err != nil {
return 0, nil, false
}
var werr error
err = sc.Read(func(fd uintptr) bool {
written, werr, handled = poll.SendFile(&c.pfd, int(fd), remain)
return true
})
if err == nil {
err = werr
}
if lr != nil {
lr.N = remain - written
}
return written, wrapSyscallError("sendfile", err), handled
}

View File

@ -49,7 +49,7 @@ func expectSendfile(t *testing.T, wantConn Conn, f func()) {
gotFD *poll.FD
gotErr error
)
poll.TestHookDidSendFile = func(dstFD *poll.FD, src int, written int64, err error, handled bool) {
poll.TestHookDidSendFile = func(dstFD *poll.FD, src uintptr, written int64, err error, handled bool) {
if called {
t.Error("internal/poll.SendFile called multiple times, want one call")
}

View File

@ -1,61 +0,0 @@
// Copyright 2011 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 net
import (
"internal/poll"
"io"
"syscall"
)
const supportsSendfile = true
// TODO: deduplicate this file with sendfile_linux.go and sendfile_unix_alt.go.
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
// 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) {
var remain int64 = 0 // by default, copy until EOF.
lr, ok := r.(*io.LimitedReader)
if ok {
remain, r = lr.N, lr.R
if remain <= 0 {
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.(syscall.Conn)
if !ok {
return 0, nil, false
}
sc, err := f.SyscallConn()
if err != nil {
return 0, nil, false
}
var werr error
err = sc.Read(func(fd uintptr) bool {
written, werr, handled = poll.SendFile(&c.pfd, syscall.Handle(fd), remain)
return true
})
if err == nil {
err = werr
}
if lr != nil {
lr.N = remain - written
}
return written, wrapSyscallError("sendfile", err), handled
}

View File

@ -48,10 +48,10 @@ func hookSendFileTB(tb testing.TB) *copyFileHook {
tb.Cleanup(func() {
poll.TestHookDidSendFile = orig
})
poll.TestHookDidSendFile = func(dstFD *poll.FD, src int, written int64, err error, handled bool) {
poll.TestHookDidSendFile = func(dstFD *poll.FD, src uintptr, written int64, err error, handled bool) {
hook.called = true
hook.dstfd = dstFD.Sysfd
hook.srcfd = src
hook.srcfd = int(src)
hook.written = written
hook.err = err
hook.handled = handled

View File

@ -111,10 +111,10 @@ func hookSendFile(t *testing.T) *sendFileHook {
t.Cleanup(func() {
poll.TestHookDidSendFile = orig
})
poll.TestHookDidSendFile = func(dstFD *poll.FD, src int, written int64, err error, handled bool) {
poll.TestHookDidSendFile = func(dstFD *poll.FD, src uintptr, written int64, err error, handled bool) {
h.called = true
h.dstfd = dstFD.Sysfd
h.srcfd = src
h.srcfd = int(src)
h.written = written
h.err = err
h.handled = handled

View File

@ -28,7 +28,7 @@ func (f *File) writeTo(w io.Writer) (written int64, handled bool, err error) {
}
rerr := sc.Read(func(fd uintptr) (done bool) {
written, err, handled = poll.SendFile(pfd, int(fd), 0)
written, err, handled = poll.SendFile(pfd, fd, 0)
return true
})

View File

@ -78,7 +78,7 @@ func (f *File) readFrom(r io.Reader) (written int64, handled bool, err error) {
// https://docs.oracle.com/cd/E88353_01/html/E37843/sendfile-3c.html and
// https://illumos.org/man/3EXT/sendfile for more details.
rerr := sc.Read(func(fd uintptr) bool {
written, err, handled = poll.SendFile(&f.pfd, int(fd), remain)
written, err, handled = poll.SendFile(&f.pfd, fd, remain)
return true
})
if lr != nil {