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

View File

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

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package net
@ -44,7 +44,7 @@ func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
var werr error var werr error
err = sc.Read(func(fd uintptr) bool { 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 return true
}) })
if err == nil { 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 gotFD *poll.FD
gotErr error 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 { if called {
t.Error("internal/poll.SendFile called multiple times, want one call") 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() { tb.Cleanup(func() {
poll.TestHookDidSendFile = orig 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.called = true
hook.dstfd = dstFD.Sysfd hook.dstfd = dstFD.Sysfd
hook.srcfd = src hook.srcfd = int(src)
hook.written = written hook.written = written
hook.err = err hook.err = err
hook.handled = handled hook.handled = handled

View File

@ -111,10 +111,10 @@ func hookSendFile(t *testing.T) *sendFileHook {
t.Cleanup(func() { t.Cleanup(func() {
poll.TestHookDidSendFile = orig 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.called = true
h.dstfd = dstFD.Sysfd h.dstfd = dstFD.Sysfd
h.srcfd = src h.srcfd = int(src)
h.written = written h.written = written
h.err = err h.err = err
h.handled = handled 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) { 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 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://docs.oracle.com/cd/E88353_01/html/E37843/sendfile-3c.html and
// https://illumos.org/man/3EXT/sendfile for more details. // https://illumos.org/man/3EXT/sendfile for more details.
rerr := sc.Read(func(fd uintptr) bool { 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 return true
}) })
if lr != nil { if lr != nil {