Skip to content

Commit 9aad012

Browse files
committed
net: reenable sendfile on Windows
Windows sendfile optimization is skipped since CL 472475, which started passing an os.fileWithoutWriteTo instead of an os.File to sendfile, and that function was only implemented for os.File. This CL fixes the issue by asserting against an interface rather than a concrete type. Some tests have been reenabled, triggering bugs in poll.SendFile which have been fixed in this CL. Fixes #67042. Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-longtest Change-Id: Id6f7a0e1e0f34a72216fa9d00c5bf36f5a994219 Reviewed-on: https://go-review.googlesource.com/c/go/+/664055 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alex Brainman <[email protected]>
1 parent d60a684 commit 9aad012

File tree

3 files changed

+77
-70
lines changed

3 files changed

+77
-70
lines changed

src/internal/poll/sendfile_windows.go

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,78 +10,80 @@ import (
1010
)
1111

1212
// SendFile wraps the TransmitFile call.
13-
func SendFile(fd *FD, src syscall.Handle, n int64) (written int64, err error) {
13+
func SendFile(fd *FD, src syscall.Handle, size int64) (written int64, err error, handled bool) {
1414
defer func() {
1515
TestHookDidSendFile(fd, 0, written, err, written > 0)
1616
}()
1717
if fd.kind == kindPipe {
1818
// TransmitFile does not work with pipes
19-
return 0, syscall.ESPIPE
19+
return 0, syscall.ESPIPE, false
2020
}
2121
if ft, _ := syscall.GetFileType(src); ft == syscall.FILE_TYPE_PIPE {
22-
return 0, syscall.ESPIPE
22+
return 0, syscall.ESPIPE, false
2323
}
2424

2525
if err := fd.writeLock(); err != nil {
26-
return 0, err
26+
return 0, err, false
2727
}
2828
defer fd.writeUnlock()
2929

30-
o := &fd.wop
31-
o.handle = src
32-
33-
// TODO(brainman): skip calling syscall.Seek if OS allows it
34-
curpos, err := syscall.Seek(o.handle, 0, io.SeekCurrent)
30+
// Get the file size so we don't read past the end of the file.
31+
var fi syscall.ByHandleFileInformation
32+
if err := syscall.GetFileInformationByHandle(src, &fi); err != nil {
33+
return 0, err, false
34+
}
35+
fileSize := int64(fi.FileSizeHigh)<<32 + int64(fi.FileSizeLow)
36+
startpos, err := syscall.Seek(src, 0, io.SeekCurrent)
3537
if err != nil {
36-
return 0, err
38+
return 0, err, false
39+
}
40+
maxSize := fileSize - startpos
41+
if size <= 0 {
42+
size = maxSize
43+
} else {
44+
size = min(size, maxSize)
3745
}
3846

39-
if n <= 0 { // We don't know the size of the file so infer it.
40-
// Find the number of bytes offset from curpos until the end of the file.
41-
n, err = syscall.Seek(o.handle, -curpos, io.SeekEnd)
42-
if err != nil {
43-
return
44-
}
45-
// Now seek back to the original position.
46-
if _, err = syscall.Seek(o.handle, curpos, io.SeekStart); err != nil {
47-
return
47+
defer func() {
48+
if written > 0 {
49+
// Some versions of Windows (Windows 10 1803) do not set
50+
// file position after TransmitFile completes.
51+
// So just use Seek to set file position.
52+
_, serr := syscall.Seek(src, startpos+written, io.SeekStart)
53+
if err != nil {
54+
err = serr
55+
}
4856
}
49-
}
57+
}()
5058

5159
// TransmitFile can be invoked in one call with at most
5260
// 2,147,483,646 bytes: the maximum value for a 32-bit integer minus 1.
5361
// See https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
5462
const maxChunkSizePerCall = int64(0x7fffffff - 1)
5563

56-
for n > 0 {
64+
o := &fd.wop
65+
o.handle = src
66+
for size > 0 {
5767
chunkSize := maxChunkSizePerCall
58-
if chunkSize > n {
59-
chunkSize = n
68+
if chunkSize > size {
69+
chunkSize = size
6070
}
6171

62-
o.o.Offset = uint32(curpos)
63-
o.o.OffsetHigh = uint32(curpos >> 32)
72+
off := startpos + written
73+
o.o.Offset = uint32(off)
74+
o.o.OffsetHigh = uint32(off >> 32)
6475

65-
nw, err := execIO(o, func(o *operation) error {
76+
n, err := execIO(o, func(o *operation) error {
6677
o.qty = uint32(chunkSize)
6778
return syscall.TransmitFile(o.fd.Sysfd, o.handle, o.qty, 0, &o.o, nil, syscall.TF_WRITE_BEHIND)
6879
})
6980
if err != nil {
70-
return written, err
71-
}
72-
73-
curpos += int64(nw)
74-
75-
// Some versions of Windows (Windows 10 1803) do not set
76-
// file position after TransmitFile completes.
77-
// So just use Seek to set file position.
78-
if _, err = syscall.Seek(o.handle, curpos, io.SeekStart); err != nil {
79-
return written, err
81+
return written, err, written > 0
8082
}
8183

82-
n -= int64(nw)
83-
written += int64(nw)
84+
size -= int64(n)
85+
written += int64(n)
8486
}
8587

86-
return
88+
return written, nil, written > 0
8789
}

src/net/sendfile_test.go

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,16 @@ func testSendfile(t *testing.T, filePath, fileHash string, size, limit int64) {
126126
// Return file data using io.Copy, which should use
127127
// sendFile if available.
128128
var sbytes int64
129-
switch runtime.GOOS {
130-
case "windows":
131-
// Windows is not using sendfile for some reason:
132-
// https://go.dev/issue/67042
133-
sbytes, err = io.Copy(conn, f)
134-
default:
135-
expectSendfile(t, conn, func() {
136-
if limit > 0 {
137-
sbytes, err = io.CopyN(conn, f, limit)
138-
if err == io.EOF && limit > size {
139-
err = nil
140-
}
141-
} else {
142-
sbytes, err = io.Copy(conn, f)
129+
expectSendfile(t, conn, func() {
130+
if limit > 0 {
131+
sbytes, err = io.CopyN(conn, f, limit)
132+
if err == io.EOF && limit > size {
133+
err = nil
143134
}
144-
})
145-
}
135+
} else {
136+
sbytes, err = io.Copy(conn, f)
137+
}
138+
})
146139
if err != nil {
147140
errc <- err
148141
return

src/net/sendfile_windows.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,55 @@ package net
77
import (
88
"internal/poll"
99
"io"
10-
"os"
1110
"syscall"
1211
)
1312

1413
const supportsSendfile = true
1514

16-
// sendFile copies the contents of r to c using the TransmitFile
15+
// TODO: deduplicate this file with sendfile_linux.go and sendfile_unix_alt.go.
16+
17+
// sendFile copies the contents of r to c using the sendfile
1718
// system call to minimize copies.
1819
//
19-
// if handled == true, sendFile returns the number of bytes copied and any
20-
// non-EOF error.
20+
// if handled == true, sendFile returns the number (potentially zero) of bytes
21+
// copied and any non-EOF error.
2122
//
2223
// if handled == false, sendFile performed no work.
23-
func sendFile(fd *netFD, r io.Reader) (written int64, err error, handled bool) {
24-
var n int64 = 0 // by default, copy until EOF.
24+
func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
25+
var remain int64 = 0 // by default, copy until EOF.
2526

2627
lr, ok := r.(*io.LimitedReader)
2728
if ok {
28-
n, r = lr.N, lr.R
29-
if n <= 0 {
29+
remain, r = lr.N, lr.R
30+
if remain <= 0 {
3031
return 0, nil, true
3132
}
3233
}
3334

34-
f, ok := r.(*os.File)
35+
// r might be an *os.File or an os.fileWithoutWriteTo.
36+
// Type assert to an interface rather than *os.File directly to handle the latter case.
37+
f, ok := r.(syscall.Conn)
3538
if !ok {
3639
return 0, nil, false
3740
}
3841

39-
written, err = poll.SendFile(&fd.pfd, syscall.Handle(f.Fd()), n)
42+
sc, err := f.SyscallConn()
4043
if err != nil {
41-
err = wrapSyscallError("transmitfile", err)
44+
return 0, nil, false
4245
}
4346

44-
// If any byte was copied, regardless of any error
45-
// encountered mid-way, handled must be set to true.
46-
handled = written > 0
47+
var werr error
48+
err = sc.Read(func(fd uintptr) bool {
49+
written, werr, handled = poll.SendFile(&c.pfd, syscall.Handle(fd), remain)
50+
return true
51+
})
52+
if err == nil {
53+
err = werr
54+
}
55+
56+
if lr != nil {
57+
lr.N = remain - written
58+
}
4759

48-
return
60+
return written, wrapSyscallError("sendfile", err), handled
4961
}

0 commit comments

Comments
 (0)