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: 4022e9b52c4375536e23162a88e5aa4d5637f134
GitHub-Pull-Request: golang/net#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>
diff --git a/internal/socket/complete_dontwait.go b/internal/socket/complete_dontwait.go
new file mode 100644
index 0000000..5b1d50a
--- /dev/null
+++ b/internal/socket/complete_dontwait.go
@@ -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
+}
diff --git a/internal/socket/complete_nodontwait.go b/internal/socket/complete_nodontwait.go
new file mode 100644
index 0000000..be63409
--- /dev/null
+++ b/internal/socket/complete_nodontwait.go
@@ -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
+}
diff --git a/internal/socket/rawconn_mmsg.go b/internal/socket/rawconn_mmsg.go
index d80a15c..3fcb51b 100644
--- a/internal/socket/rawconn_mmsg.go
+++ b/internal/socket/rawconn_mmsg.go
@@ -10,7 +10,6 @@
import (
"net"
"os"
- "syscall"
)
func (c *Conn) recvMsgs(ms []Message, flags int) (int, error) {
@@ -28,10 +27,7 @@
var n int
fn := func(s uintptr) bool {
n, operr = recvmmsg(s, hs, flags)
- if operr == syscall.EAGAIN {
- return false
- }
- return true
+ return ioComplete(flags, operr)
}
if err := c.c.Read(fn); err != nil {
return n, err
@@ -60,10 +56,7 @@
var n int
fn := func(s uintptr) bool {
n, operr = sendmmsg(s, hs, flags)
- if operr == syscall.EAGAIN {
- return false
- }
- return true
+ return ioComplete(flags, operr)
}
if err := c.c.Write(fn); err != nil {
return n, err
diff --git a/internal/socket/rawconn_msg.go b/internal/socket/rawconn_msg.go
index 2e2d61b..ba53f56 100644
--- a/internal/socket/rawconn_msg.go
+++ b/internal/socket/rawconn_msg.go
@@ -9,7 +9,6 @@
import (
"os"
- "syscall"
)
func (c *Conn) recvMsg(m *Message, flags int) error {
@@ -25,10 +24,7 @@
var n int
fn := func(s uintptr) bool {
n, operr = recvmsg(s, &h, flags)
- if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
- return false
- }
- return true
+ return ioComplete(flags, operr)
}
if err := c.c.Read(fn); err != nil {
return err
@@ -64,10 +60,7 @@
var n int
fn := func(s uintptr) bool {
n, operr = sendmsg(s, &h, flags)
- if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
- return false
- }
- return true
+ return ioComplete(flags, operr)
}
if err := c.c.Write(fn); err != nil {
return err
diff --git a/internal/socket/socket_dontwait_test.go b/internal/socket/socket_dontwait_test.go
new file mode 100644
index 0000000..8eab990
--- /dev/null
+++ b/internal/socket/socket_dontwait_test.go
@@ -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)
+ }
+ })
+ }
+}