1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
21 func TestSplice(t
*testing
.T
) {
22 t
.Run("tcp-to-tcp", func(t
*testing
.T
) { testSplice(t
, "tcp", "tcp") })
23 if !testableNetwork("unixgram") {
24 t
.Skip("skipping unix-to-tcp tests")
26 t
.Run("unix-to-tcp", func(t
*testing
.T
) { testSplice(t
, "unix", "tcp") })
27 t
.Run("no-unixpacket", testSpliceNoUnixpacket
)
28 t
.Run("no-unixgram", testSpliceNoUnixgram
)
31 func testSplice(t
*testing
.T
, upNet
, downNet
string) {
32 t
.Run("simple", spliceTestCase
{upNet
, downNet
, 128, 128, 0}.test
)
33 t
.Run("multipleWrite", spliceTestCase
{upNet
, downNet
, 4096, 1 << 20, 0}.test
)
34 t
.Run("big", spliceTestCase
{upNet
, downNet
, 5 << 20, 1 << 30, 0}.test
)
35 t
.Run("honorsLimitedReader", spliceTestCase
{upNet
, downNet
, 4096, 1 << 20, 1 << 10}.test
)
36 t
.Run("updatesLimitedReaderN", spliceTestCase
{upNet
, downNet
, 1024, 4096, 4096 + 100}.test
)
37 t
.Run("limitedReaderAtLimit", spliceTestCase
{upNet
, downNet
, 32, 128, 128}.test
)
38 t
.Run("readerAtEOF", func(t
*testing
.T
) { testSpliceReaderAtEOF(t
, upNet
, downNet
) })
39 t
.Run("issue25985", func(t
*testing
.T
) { testSpliceIssue25985(t
, upNet
, downNet
) })
42 type spliceTestCase
struct {
45 chunkSize
, totalSize
int
49 func (tc spliceTestCase
) test(t
*testing
.T
) {
50 clientUp
, serverUp
, err
:= spliceTestSocketPair(tc
.upNet
)
54 defer serverUp
.Close()
55 cleanup
, err
:= startSpliceClient(clientUp
, "w", tc
.chunkSize
, tc
.totalSize
)
60 clientDown
, serverDown
, err
:= spliceTestSocketPair(tc
.downNet
)
64 defer serverDown
.Close()
65 cleanup
, err
= startSpliceClient(clientDown
, "r", tc
.chunkSize
, tc
.totalSize
)
71 r io
.Reader
= serverUp
74 if tc
.limitReadSize
> 0 {
75 if tc
.limitReadSize
< size
{
76 size
= tc
.limitReadSize
79 r
= &io
.LimitedReader
{
80 N
: int64(tc
.limitReadSize
),
83 defer serverUp
.Close()
85 n
, err
:= io
.Copy(serverDown
, r
)
90 if want
:= int64(size
); want
!= n
{
91 t
.Errorf("want %d bytes spliced, got %d", want
, n
)
94 if tc
.limitReadSize
> 0 {
96 if tc
.limitReadSize
> size
{
97 wantN
= tc
.limitReadSize
- size
100 if n
:= r
.(*io
.LimitedReader
).N
; n
!= int64(wantN
) {
101 t
.Errorf("r.N = %d, want %d", n
, wantN
)
106 func testSpliceReaderAtEOF(t
*testing
.T
, upNet
, downNet
string) {
107 clientUp
, serverUp
, err
:= spliceTestSocketPair(upNet
)
111 defer clientUp
.Close()
112 clientDown
, serverDown
, err
:= spliceTestSocketPair(downNet
)
116 defer clientDown
.Close()
120 // We'd like to call net.splice here and check the handled return
121 // value, but we disable splice on old Linux kernels.
123 // In that case, poll.Splice and net.splice return a non-nil error
124 // and handled == false. We'd ideally like to see handled == true
125 // because the source reader is at EOF, but if we're running on an old
126 // kernel, and splice is disabled, we won't see EOF from net.splice,
127 // because we won't touch the reader at all.
129 // Trying to untangle the errors from net.splice and match them
130 // against the errors created by the poll package would be brittle,
131 // so this is a higher level test.
133 // The following ReadFrom should return immediately, regardless of
134 // whether splice is disabled or not. The other side should then
135 // get a goodbye signal. Test for the goodbye signal.
138 serverDown
.(io
.ReaderFrom
).ReadFrom(serverUp
)
139 io
.WriteString(serverDown
, msg
)
143 buf
:= make([]byte, 3)
144 _
, err
= io
.ReadFull(clientDown
, buf
)
146 t
.Errorf("clientDown: %v", err
)
148 if string(buf
) != msg
{
149 t
.Errorf("clientDown got %q, want %q", buf
, msg
)
153 func testSpliceIssue25985(t
*testing
.T
, upNet
, downNet
string) {
154 front
, err
:= newLocalListener(upNet
)
159 back
, err
:= newLocalListener(downNet
)
165 var wg sync
.WaitGroup
169 src
, err
:= front
.Accept()
173 dst
, err
:= Dial(downNet
, back
.Addr().String())
191 toFront
, err
:= Dial(upNet
, front
.Addr().String())
196 io
.WriteString(toFront
, "foo")
199 fromProxy
, err
:= back
.Accept()
203 defer fromProxy
.Close()
205 _
, err
= ioutil
.ReadAll(fromProxy
)
213 func testSpliceNoUnixpacket(t
*testing
.T
) {
214 clientUp
, serverUp
, err
:= spliceTestSocketPair("unixpacket")
218 defer clientUp
.Close()
219 defer serverUp
.Close()
220 clientDown
, serverDown
, err
:= spliceTestSocketPair("tcp")
224 defer clientDown
.Close()
225 defer serverDown
.Close()
226 // If splice called poll.Splice here, we'd get err == syscall.EINVAL
227 // and handled == false. If poll.Splice gets an EINVAL on the first
228 // try, it assumes the kernel it's running on doesn't support splice
229 // for unix sockets and returns handled == false. This works for our
230 // purposes by somewhat of an accident, but is not entirely correct.
232 // What we want is err == nil and handled == false, i.e. we never
233 // called poll.Splice, because we know the unix socket's network.
234 _
, err
, handled
:= splice(serverDown
.(*TCPConn
).fd
, serverUp
)
235 if err
!= nil || handled
!= false {
236 t
.Fatalf("got err = %v, handled = %t, want nil error, handled == false", err
, handled
)
240 func testSpliceNoUnixgram(t
*testing
.T
) {
241 addr
, err
:= ResolveUnixAddr("unixgram", testUnixAddr())
245 defer os
.Remove(addr
.Name
)
246 up
, err
:= ListenUnixgram("unixgram", addr
)
251 clientDown
, serverDown
, err
:= spliceTestSocketPair("tcp")
255 defer clientDown
.Close()
256 defer serverDown
.Close()
257 // Analogous to testSpliceNoUnixpacket.
258 _
, err
, handled
:= splice(serverDown
.(*TCPConn
).fd
, up
)
259 if err
!= nil || handled
!= false {
260 t
.Fatalf("got err = %v, handled = %t, want nil error, handled == false", err
, handled
)
264 func BenchmarkSplice(b
*testing
.B
) {
265 testHookUninstaller
.Do(uninstallTestHooks
)
267 b
.Run("tcp-to-tcp", func(b
*testing
.B
) { benchSplice(b
, "tcp", "tcp") })
268 b
.Run("unix-to-tcp", func(b
*testing
.B
) { benchSplice(b
, "unix", "tcp") })
271 func benchSplice(b
*testing
.B
, upNet
, downNet
string) {
272 for i
:= 0; i
<= 10; i
++ {
273 chunkSize
:= 1 << uint(i
+10)
274 tc
:= spliceTestCase
{
277 chunkSize
: chunkSize
,
280 b
.Run(strconv
.Itoa(chunkSize
), tc
.bench
)
284 func (tc spliceTestCase
) bench(b
*testing
.B
) {
285 // To benchmark the genericReadFrom code path, set this to false.
288 clientUp
, serverUp
, err
:= spliceTestSocketPair(tc
.upNet
)
292 defer serverUp
.Close()
294 cleanup
, err
:= startSpliceClient(clientUp
, "w", tc
.chunkSize
, tc
.chunkSize
*b
.N
)
300 clientDown
, serverDown
, err
:= spliceTestSocketPair(tc
.downNet
)
304 defer serverDown
.Close()
306 cleanup
, err
= startSpliceClient(clientDown
, "r", tc
.chunkSize
, tc
.chunkSize
*b
.N
)
312 b
.SetBytes(int64(tc
.chunkSize
))
316 _
, err
:= io
.Copy(serverDown
, serverUp
)
321 type onlyReader
struct {
324 _
, err
:= io
.Copy(serverDown
, onlyReader
{serverUp
})
331 func spliceTestSocketPair(net
string) (client
, server Conn
, err error
) {
332 ln
, err
:= newLocalListener(net
)
338 acceptDone
:= make(chan struct{})
340 server
, serr
= ln
.Accept()
341 acceptDone
<- struct{}{}
343 client
, cerr
= Dial(ln
.Addr().Network(), ln
.Addr().String())
349 return nil, nil, cerr
355 return nil, nil, serr
357 return client
, server
, nil
360 func startSpliceClient(conn Conn
, op
string, chunkSize
, totalSize
int) (func(), error
) {
361 f
, err
:= conn
.(interface{ File() (*os
.File
, error
) }).File()
366 cmd
:= exec
.Command(os
.Args
[0], os
.Args
[1:]...)
367 cmd
.Env
= append(os
.Environ(), []string{
368 "GO_NET_TEST_SPLICE=1",
369 "GO_NET_TEST_SPLICE_OP=" + op
,
370 "GO_NET_TEST_SPLICE_CHUNK_SIZE=" + strconv
.Itoa(chunkSize
),
371 "GO_NET_TEST_SPLICE_TOTAL_SIZE=" + strconv
.Itoa(totalSize
),
372 "TMPDIR=" + os
.Getenv("TMPDIR"),
374 cmd
.ExtraFiles
= append(cmd
.ExtraFiles
, f
)
375 cmd
.Stdout
= os
.Stdout
376 cmd
.Stderr
= os
.Stderr
378 if err
:= cmd
.Start(); err
!= nil {
382 donec
:= make(chan struct{})
393 case <-time
.After(5 * time
.Second
):
394 log
.Printf("killing splice client after 5 second shutdown timeout")
398 case <-time
.After(5 * time
.Second
):
399 log
.Printf("splice client didn't die after 10 seconds")
406 if os
.Getenv("GO_NET_TEST_SPLICE") == "" {
411 f
:= os
.NewFile(uintptr(3), "splice-test-conn")
414 conn
, err
:= FileConn(f
)
420 if chunkSize
, err
= strconv
.Atoi(os
.Getenv("GO_NET_TEST_SPLICE_CHUNK_SIZE")); err
!= nil {
423 buf
:= make([]byte, chunkSize
)
426 if totalSize
, err
= strconv
.Atoi(os
.Getenv("GO_NET_TEST_SPLICE_TOTAL_SIZE")); err
!= nil {
430 var fn
func([]byte) (int, error
)
431 switch op
:= os
.Getenv("GO_NET_TEST_SPLICE_OP"); op
{
439 log
.Fatalf("unknown op %q", op
)
443 for count
:= 0; count
< totalSize
; count
+= n
{
444 if count
+chunkSize
> totalSize
{
445 buf
= buf
[:totalSize
-count
]
449 if n
, err
= fn(buf
); err
!= nil {