Skip to content

Commit f664031

Browse files
panjf2000neild
authored andcommitted
net,os: arrange zero-copy of os.File and net.TCPConn to net.UnixConn
Fixes #58808 goos: linux goarch: amd64 pkg: net cpu: DO-Premium-Intel │ old │ new │ │ sec/op │ sec/op vs base │ Splice/tcp-to-unix/1024-4 3.783µ ± 10% 3.201µ ± 7% -15.40% (p=0.001 n=10) Splice/tcp-to-unix/2048-4 3.967µ ± 13% 3.818µ ± 16% ~ (p=0.971 n=10) Splice/tcp-to-unix/4096-4 4.988µ ± 16% 4.590µ ± 11% ~ (p=0.089 n=10) Splice/tcp-to-unix/8192-4 6.981µ ± 13% 5.236µ ± 9% -25.00% (p=0.000 n=10) Splice/tcp-to-unix/16384-4 10.192µ ± 9% 7.350µ ± 7% -27.89% (p=0.000 n=10) Splice/tcp-to-unix/32768-4 19.65µ ± 13% 10.28µ ± 16% -47.69% (p=0.000 n=10) Splice/tcp-to-unix/65536-4 41.89µ ± 18% 15.70µ ± 13% -62.52% (p=0.000 n=10) Splice/tcp-to-unix/131072-4 90.05µ ± 11% 29.55µ ± 10% -67.18% (p=0.000 n=10) Splice/tcp-to-unix/262144-4 170.24µ ± 15% 52.66µ ± 4% -69.06% (p=0.000 n=10) Splice/tcp-to-unix/524288-4 326.4µ ± 13% 109.3µ ± 11% -66.52% (p=0.000 n=10) Splice/tcp-to-unix/1048576-4 651.4µ ± 9% 228.3µ ± 14% -64.95% (p=0.000 n=10) geomean 29.42µ 15.62µ -46.90% │ old │ new │ │ B/s │ B/s vs base │ Splice/tcp-to-unix/1024-4 258.2Mi ± 11% 305.2Mi ± 8% +18.21% (p=0.001 n=10) Splice/tcp-to-unix/2048-4 492.5Mi ± 15% 511.7Mi ± 13% ~ (p=0.971 n=10) Splice/tcp-to-unix/4096-4 783.5Mi ± 14% 851.2Mi ± 12% ~ (p=0.089 n=10) Splice/tcp-to-unix/8192-4 1.093Gi ± 11% 1.458Gi ± 8% +33.36% (p=0.000 n=10) Splice/tcp-to-unix/16384-4 1.497Gi ± 9% 2.076Gi ± 7% +38.67% (p=0.000 n=10) Splice/tcp-to-unix/32768-4 1.553Gi ± 11% 2.969Gi ± 14% +91.17% (p=0.000 n=10) Splice/tcp-to-unix/65536-4 1.458Gi ± 23% 3.888Gi ± 11% +166.69% (p=0.000 n=10) Splice/tcp-to-unix/131072-4 1.356Gi ± 10% 4.131Gi ± 9% +204.72% (p=0.000 n=10) Splice/tcp-to-unix/262144-4 1.434Gi ± 13% 4.637Gi ± 4% +223.32% (p=0.000 n=10) Splice/tcp-to-unix/524288-4 1.497Gi ± 15% 4.468Gi ± 10% +198.47% (p=0.000 n=10) Splice/tcp-to-unix/1048576-4 1.501Gi ± 10% 4.277Gi ± 16% +184.88% (p=0.000 n=10) geomean 1.038Gi 1.954Gi +88.28% │ old │ new │ │ B/op │ B/op vs base │ Splice/tcp-to-unix/1024-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/2048-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/4096-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/8192-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/16384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/32768-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/65536-4 1.000 ± ? 0.000 ± 0% -100.00% (p=0.001 n=10) Splice/tcp-to-unix/131072-4 2.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) Splice/tcp-to-unix/262144-4 4.000 ± 25% 0.000 ± 0% -100.00% (p=0.000 n=10) Splice/tcp-to-unix/524288-4 7.500 ± 33% 0.000 ± 0% -100.00% (p=0.000 n=10) Splice/tcp-to-unix/1048576-4 17.00 ± 12% 0.00 ± 0% -100.00% (p=0.000 n=10) geomean ² ? ² ³ ¹ all samples are equal ² summaries must be >0 to compute geomean ³ ratios must be >0 to compute geomean │ old │ new │ │ allocs/op │ allocs/op vs base │ Splice/tcp-to-unix/1024-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/2048-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/4096-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/8192-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/16384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/32768-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/65536-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/131072-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/262144-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/524288-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ Splice/tcp-to-unix/1048576-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean Change-Id: I829061b009a0929a8ef1a15c183793c0b9104dde Reviewed-on: https://go-review.googlesource.com/c/go/+/472475 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Bryan Mills <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent f67b2d8 commit f664031

18 files changed

+461
-67
lines changed

api/next/58808.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pkg net, method (*TCPConn) WriteTo(io.Writer) (int64, error) #58808
2+
pkg os, method (*File) WriteTo(io.Writer) (int64, error) #58808

src/internal/poll/fd.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,14 @@ func consume(v *[][]byte, n int64) {
8181

8282
// TestHookDidWritev is a hook for testing writev.
8383
var TestHookDidWritev = func(wrote int) {}
84+
85+
// String is an internal string definition for methods/functions
86+
// that is not intended for use outside the standard libraries.
87+
//
88+
// Other packages in std that import internal/poll and have some
89+
// exported APIs (now we've got some in net.rawConn) which are only used
90+
// internally and are not intended to be used outside the standard libraries,
91+
// Therefore, we make those APIs use internal types like poll.FD or poll.String
92+
// in their function signatures to disable the usability of these APIs from
93+
// external codebase.
94+
type String string

src/net/http/transfer_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,12 @@ func TestTransferWriterWriteBodyReaderTypes(t *testing.T) {
264264
actualReader = reflect.TypeOf(lr.R)
265265
} else {
266266
actualReader = reflect.TypeOf(mw.CalledReader)
267+
// We have to handle this special case for genericWriteTo in os,
268+
// this struct is introduced to support a zero-copy optimization,
269+
// check out https://go.dev/issue/58808 for details.
270+
if actualReader.Kind() == reflect.Struct && actualReader.PkgPath() == "os" && actualReader.Name() == "fileWithoutWriteTo" {
271+
actualReader = actualReader.Field(1).Type
272+
}
267273
}
268274

269275
if tc.expectedReader != actualReader {

src/net/net.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -664,15 +664,53 @@ var errClosed = poll.ErrNetClosing
664664
// errors.Is(err, net.ErrClosed).
665665
var ErrClosed error = errClosed
666666

667-
type writerOnly struct {
668-
io.Writer
667+
// noReadFrom can be embedded alongside another type to
668+
// hide the ReadFrom method of that other type.
669+
type noReadFrom struct{}
670+
671+
// ReadFrom hides another ReadFrom method.
672+
// It should never be called.
673+
func (noReadFrom) ReadFrom(io.Reader) (int64, error) {
674+
panic("can't happen")
675+
}
676+
677+
// tcpConnWithoutReadFrom implements all the methods of *TCPConn other
678+
// than ReadFrom. This is used to permit ReadFrom to call io.Copy
679+
// without leading to a recursive call to ReadFrom.
680+
type tcpConnWithoutReadFrom struct {
681+
noReadFrom
682+
*TCPConn
669683
}
670684

671685
// Fallback implementation of io.ReaderFrom's ReadFrom, when sendfile isn't
672686
// applicable.
673-
func genericReadFrom(w io.Writer, r io.Reader) (n int64, err error) {
687+
func genericReadFrom(c *TCPConn, r io.Reader) (n int64, err error) {
674688
// Use wrapper to hide existing r.ReadFrom from io.Copy.
675-
return io.Copy(writerOnly{w}, r)
689+
return io.Copy(tcpConnWithoutReadFrom{TCPConn: c}, r)
690+
}
691+
692+
// noWriteTo can be embedded alongside another type to
693+
// hide the WriteTo method of that other type.
694+
type noWriteTo struct{}
695+
696+
// WriteTo hides another WriteTo method.
697+
// It should never be called.
698+
func (noWriteTo) WriteTo(io.Writer) (int64, error) {
699+
panic("can't happen")
700+
}
701+
702+
// tcpConnWithoutWriteTo implements all the methods of *TCPConn other
703+
// than WriteTo. This is used to permit WriteTo to call io.Copy
704+
// without leading to a recursive call to WriteTo.
705+
type tcpConnWithoutWriteTo struct {
706+
noWriteTo
707+
*TCPConn
708+
}
709+
710+
// Fallback implementation of io.WriterTo's WriteTo, when zero-copy isn't applicable.
711+
func genericWriteTo(c *TCPConn, w io.Writer) (n int64, err error) {
712+
// Use wrapper to hide existing w.WriteTo from io.Copy.
713+
return io.Copy(w, tcpConnWithoutWriteTo{TCPConn: c})
676714
}
677715

678716
// Limit the number of concurrent cgo-using goroutines, because

src/net/rawconn.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ func newRawConn(fd *netFD) *rawConn {
7979
return &rawConn{fd: fd}
8080
}
8181

82+
// Network returns the network type of the underlying connection.
83+
//
84+
// Other packages in std that import internal/poll and are unable to
85+
// import net (such as os) can use a type assertion to access this
86+
// extension method so that they can distinguish different socket types.
87+
//
88+
// Network is not intended for use outside the standard library.
89+
func (c *rawConn) Network() poll.String {
90+
return poll.String(c.fd.net)
91+
}
92+
8293
type rawListener struct {
8394
rawConn
8495
}

src/net/sendfile_linux_test.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,36 @@ import (
1414
)
1515

1616
func BenchmarkSendFile(b *testing.B) {
17+
b.Run("file-to-tcp", func(b *testing.B) { benchmarkSendFile(b, "tcp") })
18+
b.Run("file-to-unix", func(b *testing.B) { benchmarkSendFile(b, "unix") })
19+
}
20+
21+
func benchmarkSendFile(b *testing.B, proto string) {
1722
for i := 0; i <= 10; i++ {
1823
size := 1 << (i + 10)
19-
bench := sendFileBench{chunkSize: size}
24+
bench := sendFileBench{
25+
proto: proto,
26+
chunkSize: size,
27+
}
2028
b.Run(strconv.Itoa(size), bench.benchSendFile)
2129
}
2230
}
2331

2432
type sendFileBench struct {
33+
proto string
2534
chunkSize int
2635
}
2736

2837
func (bench sendFileBench) benchSendFile(b *testing.B) {
2938
fileSize := b.N * bench.chunkSize
3039
f := createTempFile(b, fileSize)
31-
fileName := f.Name()
32-
defer os.Remove(fileName)
33-
defer f.Close()
3440

35-
client, server := spliceTestSocketPair(b, "tcp")
41+
client, server := spliceTestSocketPair(b, bench.proto)
3642
defer server.Close()
3743

3844
cleanUp, err := startSpliceClient(client, "r", bench.chunkSize, fileSize)
3945
if err != nil {
46+
client.Close()
4047
b.Fatal(err)
4148
}
4249
defer cleanUp()
@@ -51,15 +58,18 @@ func (bench sendFileBench) benchSendFile(b *testing.B) {
5158
b.Fatalf("failed to copy data with sendfile, error: %v", err)
5259
}
5360
if sent != int64(fileSize) {
54-
b.Fatalf("bytes sent mismatch\n\texpect: %d\n\tgot: %d", fileSize, sent)
61+
b.Fatalf("bytes sent mismatch, got: %d, want: %d", sent, fileSize)
5562
}
5663
}
5764

5865
func createTempFile(b *testing.B, size int) *os.File {
59-
f, err := os.CreateTemp("", "linux-sendfile-test")
66+
f, err := os.CreateTemp(b.TempDir(), "linux-sendfile-bench")
6067
if err != nil {
6168
b.Fatalf("failed to create temporary file: %v", err)
6269
}
70+
b.Cleanup(func() {
71+
f.Close()
72+
})
6373

6474
data := make([]byte, size)
6575
if _, err := f.Write(data); err != nil {

src/net/splice_linux.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import (
99
"io"
1010
)
1111

12-
// splice transfers data from r to c using the splice system call to minimize
13-
// copies from and to userspace. c must be a TCP connection. Currently, splice
14-
// is only enabled if r is a TCP or a stream-oriented Unix connection.
12+
// spliceFrom transfers data from r to c using the splice system call to minimize
13+
// copies from and to userspace. c must be a TCP connection.
14+
// Currently, spliceFrom is only enabled if r is a TCP or a stream-oriented Unix connection.
1515
//
16-
// If splice returns handled == false, it has performed no work.
17-
func splice(c *netFD, r io.Reader) (written int64, err error, handled bool) {
16+
// If spliceFrom returns handled == false, it has performed no work.
17+
func spliceFrom(c *netFD, r io.Reader) (written int64, err error, handled bool) {
1818
var remain int64 = 1<<63 - 1 // by default, copy until EOF
1919
lr, ok := r.(*io.LimitedReader)
2020
if ok {
@@ -25,14 +25,17 @@ func splice(c *netFD, r io.Reader) (written int64, err error, handled bool) {
2525
}
2626

2727
var s *netFD
28-
if tc, ok := r.(*TCPConn); ok {
29-
s = tc.fd
30-
} else if uc, ok := r.(*UnixConn); ok {
31-
if uc.fd.net != "unix" {
28+
switch v := r.(type) {
29+
case *TCPConn:
30+
s = v.fd
31+
case tcpConnWithoutWriteTo:
32+
s = v.fd
33+
case *UnixConn:
34+
if v.fd.net != "unix" {
3235
return 0, nil, false
3336
}
34-
s = uc.fd
35-
} else {
37+
s = v.fd
38+
default:
3639
return 0, nil, false
3740
}
3841

@@ -42,3 +45,18 @@ func splice(c *netFD, r io.Reader) (written int64, err error, handled bool) {
4245
}
4346
return written, wrapSyscallError(sc, err), handled
4447
}
48+
49+
// spliceTo transfers data from c to w using the splice system call to minimize
50+
// copies from and to userspace. c must be a TCP connection.
51+
// Currently, spliceTo is only enabled if w is a stream-oriented Unix connection.
52+
//
53+
// If spliceTo returns handled == false, it has performed no work.
54+
func spliceTo(w io.Writer, c *netFD) (written int64, err error, handled bool) {
55+
uc, ok := w.(*UnixConn)
56+
if !ok || uc.fd.net != "unix" {
57+
return
58+
}
59+
60+
written, handled, sc, err := poll.Splice(&uc.fd.pfd, &c.pfd, 1<<63-1)
61+
return written, wrapSyscallError(sc, err), handled
62+
}

src/net/splice_stub.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ package net
88

99
import "io"
1010

11-
func splice(c *netFD, r io.Reader) (int64, error, bool) {
11+
func spliceFrom(_ *netFD, _ io.Reader) (int64, error, bool) {
12+
return 0, nil, false
13+
}
14+
15+
func spliceTo(_ io.Writer, _ *netFD) (int64, error, bool) {
1216
return 0, nil, false
1317
}

src/net/splice_test.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func TestSplice(t *testing.T) {
2323
t.Skip("skipping unix-to-tcp tests")
2424
}
2525
t.Run("unix-to-tcp", func(t *testing.T) { testSplice(t, "unix", "tcp") })
26+
t.Run("tcp-to-unix", func(t *testing.T) { testSplice(t, "tcp", "unix") })
2627
t.Run("tcp-to-file", func(t *testing.T) { testSpliceToFile(t, "tcp", "file") })
2728
t.Run("unix-to-file", func(t *testing.T) { testSpliceToFile(t, "unix", "file") })
2829
t.Run("no-unixpacket", testSpliceNoUnixpacket)
@@ -159,23 +160,30 @@ func (tc spliceTestCase) testFile(t *testing.T) {
159160
}
160161

161162
func testSpliceReaderAtEOF(t *testing.T, upNet, downNet string) {
163+
// UnixConn doesn't implement io.ReaderFrom, which will fail
164+
// the following test in asserting a UnixConn to be an io.ReaderFrom,
165+
// so skip this test.
166+
if upNet == "unix" || downNet == "unix" {
167+
t.Skip("skipping test on unix socket")
168+
}
169+
162170
clientUp, serverUp := spliceTestSocketPair(t, upNet)
163171
defer clientUp.Close()
164172
clientDown, serverDown := spliceTestSocketPair(t, downNet)
165173
defer clientDown.Close()
166174

167175
serverUp.Close()
168176

169-
// We'd like to call net.splice here and check the handled return
177+
// We'd like to call net.spliceFrom here and check the handled return
170178
// value, but we disable splice on old Linux kernels.
171179
//
172-
// In that case, poll.Splice and net.splice return a non-nil error
180+
// In that case, poll.Splice and net.spliceFrom return a non-nil error
173181
// and handled == false. We'd ideally like to see handled == true
174182
// because the source reader is at EOF, but if we're running on an old
175-
// kernel, and splice is disabled, we won't see EOF from net.splice,
183+
// kernel, and splice is disabled, we won't see EOF from net.spliceFrom,
176184
// because we won't touch the reader at all.
177185
//
178-
// Trying to untangle the errors from net.splice and match them
186+
// Trying to untangle the errors from net.spliceFrom and match them
179187
// against the errors created by the poll package would be brittle,
180188
// so this is a higher level test.
181189
//
@@ -268,7 +276,7 @@ func testSpliceNoUnixpacket(t *testing.T) {
268276
//
269277
// What we want is err == nil and handled == false, i.e. we never
270278
// called poll.Splice, because we know the unix socket's network.
271-
_, err, handled := splice(serverDown.(*TCPConn).fd, serverUp)
279+
_, err, handled := spliceFrom(serverDown.(*TCPConn).fd, serverUp)
272280
if err != nil || handled != false {
273281
t.Fatalf("got err = %v, handled = %t, want nil error, handled == false", err, handled)
274282
}
@@ -289,7 +297,7 @@ func testSpliceNoUnixgram(t *testing.T) {
289297
defer clientDown.Close()
290298
defer serverDown.Close()
291299
// Analogous to testSpliceNoUnixpacket.
292-
_, err, handled := splice(serverDown.(*TCPConn).fd, up)
300+
_, err, handled := spliceFrom(serverDown.(*TCPConn).fd, up)
293301
if err != nil || handled != false {
294302
t.Fatalf("got err = %v, handled = %t, want nil error, handled == false", err, handled)
295303
}
@@ -300,6 +308,7 @@ func BenchmarkSplice(b *testing.B) {
300308

301309
b.Run("tcp-to-tcp", func(b *testing.B) { benchSplice(b, "tcp", "tcp") })
302310
b.Run("unix-to-tcp", func(b *testing.B) { benchSplice(b, "unix", "tcp") })
311+
b.Run("tcp-to-unix", func(b *testing.B) { benchSplice(b, "tcp", "unix") })
303312
}
304313

305314
func benchSplice(b *testing.B, upNet, downNet string) {

src/net/tcpsock.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) {
134134
return n, err
135135
}
136136

137+
// WriteTo implements the io.WriterTo WriteTo method.
138+
func (c *TCPConn) WriteTo(w io.Writer) (int64, error) {
139+
if !c.ok() {
140+
return 0, syscall.EINVAL
141+
}
142+
n, err := c.writeTo(w)
143+
if err != nil && err != io.EOF {
144+
err = &OpError{Op: "writeto", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
145+
}
146+
return n, err
147+
}
148+
137149
// CloseRead shuts down the reading side of the TCP connection.
138150
// Most callers should just use Close.
139151
func (c *TCPConn) CloseRead() error {

src/net/tcpsock_plan9.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ func (c *TCPConn) readFrom(r io.Reader) (int64, error) {
1414
return genericReadFrom(c, r)
1515
}
1616

17+
func (c *TCPConn) writeTo(w io.Writer) (int64, error) {
18+
return genericWriteTo(c, w)
19+
}
20+
1721
func (sd *sysDialer) dialTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
1822
if h := sd.testHookDialTCP; h != nil {
1923
return h(ctx, sd.network, laddr, raddr)

src/net/tcpsock_posix.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (a *TCPAddr) toLocal(net string) sockaddr {
4545
}
4646

4747
func (c *TCPConn) readFrom(r io.Reader) (int64, error) {
48-
if n, err, handled := splice(c.fd, r); handled {
48+
if n, err, handled := spliceFrom(c.fd, r); handled {
4949
return n, err
5050
}
5151
if n, err, handled := sendFile(c.fd, r); handled {
@@ -54,6 +54,13 @@ func (c *TCPConn) readFrom(r io.Reader) (int64, error) {
5454
return genericReadFrom(c, r)
5555
}
5656

57+
func (c *TCPConn) writeTo(w io.Writer) (int64, error) {
58+
if n, err, handled := spliceTo(w, c.fd); handled {
59+
return n, err
60+
}
61+
return genericWriteTo(c, w)
62+
}
63+
5764
func (sd *sysDialer) dialTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
5865
if h := sd.testHookDialTCP; h != nil {
5966
return h(ctx, sd.network, laddr, raddr)

src/os/export_linux_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
package os
66

77
var (
8-
PollCopyFileRangeP = &pollCopyFileRange
9-
PollSpliceFile = &pollSplice
10-
GetPollFDForTest = getPollFD
8+
PollCopyFileRangeP = &pollCopyFileRange
9+
PollSpliceFile = &pollSplice
10+
PollSendFile = &pollSendFile
11+
GetPollFDAndNetwork = getPollFDAndNetwork
1112
)

0 commit comments

Comments
 (0)