-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/socket: support MSG_DONTWAIT
Explicitly handle MSG_DONTWAIT in read and send calls on platforms where this is defined, to get the per-call non-blocking behavior as would be expected when calling readmsg/sendmsg in C. When MSG_DONTWAIT is set, we always return true from the function passed to syscall.RawConn.Read/Write, to avoid entering the polling state. Fixes golang/go#46891 Change-Id: I4809577477554db1c45b6f4825a03d98208199d7 GitHub-Last-Rev: 4022e9b GitHub-Pull-Request: #108 Reviewed-on: https://go-review.googlesource.com/c/net/+/333469 Run-TryBot: Ian Lance Taylor <iant@golang.org> Run-TryBot: Damien Neil <dneil@google.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Damien Neil <dneil@google.com> Trust: Damien Neil <dneil@google.com>
- Loading branch information
1 parent
c6fcb2d
commit aaa1db6
Showing
5 changed files
with
177 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright 2021 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. | ||
|
||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris | ||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris | ||
|
||
package socket | ||
|
||
import ( | ||
"syscall" | ||
) | ||
|
||
// ioComplete checks the flags and result of a syscall, to be used as return | ||
// value in a syscall.RawConn.Read or Write callback. | ||
func ioComplete(flags int, operr error) bool { | ||
if flags&syscall.MSG_DONTWAIT != 0 { | ||
// Caller explicitly said don't wait, so always return immediately. | ||
return true | ||
} | ||
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK { | ||
// No data available, block for I/O and try again. | ||
return false | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright 2021 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. | ||
|
||
//go:build aix || windows || zos | ||
// +build aix windows zos | ||
|
||
package socket | ||
|
||
import ( | ||
"syscall" | ||
) | ||
|
||
// ioComplete checks the flags and result of a syscall, to be used as return | ||
// value in a syscall.RawConn.Read or Write callback. | ||
func ioComplete(flags int, operr error) bool { | ||
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK { | ||
// No data available, block for I/O and try again. | ||
return false | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright 2021 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. | ||
|
||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris | ||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris | ||
|
||
package socket_test | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"net" | ||
"runtime" | ||
"syscall" | ||
"testing" | ||
|
||
"golang.org/x/net/internal/socket" | ||
"golang.org/x/net/nettest" | ||
) | ||
|
||
func TestUDPDontwait(t *testing.T) { | ||
c, err := nettest.NewLocalPacketListener("udp") | ||
if err != nil { | ||
t.Skipf("not supported on %s/%s: %v", runtime.GOOS, runtime.GOARCH, err) | ||
} | ||
defer c.Close() | ||
cc, err := socket.NewConn(c.(*net.UDPConn)) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
isErrWouldblock := func(err error) bool { | ||
var errno syscall.Errno | ||
return errors.As(err, &errno) && (errno == syscall.EAGAIN || errno == syscall.EWOULDBLOCK) | ||
} | ||
|
||
t.Run("Message-dontwait", func(t *testing.T) { | ||
// Read before something was sent; expect EWOULDBLOCK | ||
b := make([]byte, 32) | ||
rm := socket.Message{ | ||
Buffers: [][]byte{b}, | ||
} | ||
if err := cc.RecvMsg(&rm, syscall.MSG_DONTWAIT); !isErrWouldblock(err) { | ||
t.Fatal(err) | ||
} | ||
// To trigger EWOULDBLOCK by SendMsg, we have to send faster than what the | ||
// system/network is able to process. Whether or not we can trigger this | ||
// depends on the system, specifically on write buffer sizes and the speed | ||
// of the network interface. | ||
// We cannot expect to quickly and reliably trigger this, especially not | ||
// because this test sends data over a (fast) loopback. Consequently, we | ||
// only check that sending with MSG_DONTWAIT works at all and don't attempt | ||
// testing that we would eventually get EWOULDBLOCK here. | ||
data := []byte("HELLO-R-U-THERE") | ||
wm := socket.Message{ | ||
Buffers: [][]byte{data}, | ||
Addr: c.LocalAddr(), | ||
} | ||
// Send one message, repeat until we don't get EWOULDBLOCK. This will likely succeed at the first attempt. | ||
for { | ||
err := cc.SendMsg(&wm, syscall.MSG_DONTWAIT) | ||
if err == nil { | ||
break | ||
} else if !isErrWouldblock(err) { | ||
t.Fatal(err) | ||
} | ||
} | ||
// Read the message now available; again, this will likely succeed at the first attempt. | ||
for { | ||
err := cc.RecvMsg(&rm, syscall.MSG_DONTWAIT) | ||
if err == nil { | ||
break | ||
} else if !isErrWouldblock(err) { | ||
t.Fatal(err) | ||
} | ||
} | ||
if !bytes.Equal(b[:rm.N], data) { | ||
t.Fatalf("got %#v; want %#v", b[:rm.N], data) | ||
} | ||
}) | ||
switch runtime.GOOS { | ||
case "android", "linux": | ||
t.Run("Messages", func(t *testing.T) { | ||
data := []byte("HELLO-R-U-THERE") | ||
wmbs := bytes.SplitAfter(data, []byte("-")) | ||
wms := []socket.Message{ | ||
{Buffers: wmbs[:1], Addr: c.LocalAddr()}, | ||
{Buffers: wmbs[1:], Addr: c.LocalAddr()}, | ||
} | ||
b := make([]byte, 32) | ||
rmbs := [][][]byte{{b[:len(wmbs[0])]}, {b[len(wmbs[0]):]}} | ||
rms := []socket.Message{ | ||
{Buffers: rmbs[0]}, | ||
{Buffers: rmbs[1]}, | ||
} | ||
_, err := cc.RecvMsgs(rms, syscall.MSG_DONTWAIT) | ||
if !isErrWouldblock(err) { | ||
t.Fatal(err) | ||
} | ||
for ntot := 0; ntot < len(wms); { | ||
n, err := cc.SendMsgs(wms[ntot:], syscall.MSG_DONTWAIT) | ||
if err == nil { | ||
ntot += n | ||
} else if !isErrWouldblock(err) { | ||
t.Fatal(err) | ||
} | ||
} | ||
for ntot := 0; ntot < len(rms); { | ||
n, err := cc.RecvMsgs(rms[ntot:], syscall.MSG_DONTWAIT) | ||
if err == nil { | ||
ntot += n | ||
} else if !isErrWouldblock(err) { | ||
t.Fatal(err) | ||
} | ||
} | ||
nn := 0 | ||
for i := 0; i < len(rms); i++ { | ||
nn += rms[i].N | ||
} | ||
if !bytes.Equal(b[:nn], data) { | ||
t.Fatalf("got %#v; want %#v", b[:nn], data) | ||
} | ||
}) | ||
} | ||
} |