Skip to content

Commit aaa1db6

Browse files
matzfianlancetaylor
authored andcommitted
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 <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Damien Neil <[email protected]> Trust: Damien Neil <[email protected]>
1 parent c6fcb2d commit aaa1db6

File tree

5 files changed

+177
-18
lines changed

5 files changed

+177
-18
lines changed

internal/socket/complete_dontwait.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2021 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.
4+
5+
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
6+
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
7+
8+
package socket
9+
10+
import (
11+
"syscall"
12+
)
13+
14+
// ioComplete checks the flags and result of a syscall, to be used as return
15+
// value in a syscall.RawConn.Read or Write callback.
16+
func ioComplete(flags int, operr error) bool {
17+
if flags&syscall.MSG_DONTWAIT != 0 {
18+
// Caller explicitly said don't wait, so always return immediately.
19+
return true
20+
}
21+
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
22+
// No data available, block for I/O and try again.
23+
return false
24+
}
25+
return true
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2021 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.
4+
5+
//go:build aix || windows || zos
6+
// +build aix windows zos
7+
8+
package socket
9+
10+
import (
11+
"syscall"
12+
)
13+
14+
// ioComplete checks the flags and result of a syscall, to be used as return
15+
// value in a syscall.RawConn.Read or Write callback.
16+
func ioComplete(flags int, operr error) bool {
17+
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
18+
// No data available, block for I/O and try again.
19+
return false
20+
}
21+
return true
22+
}

internal/socket/rawconn_mmsg.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ package socket
1010
import (
1111
"net"
1212
"os"
13-
"syscall"
1413
)
1514

1615
func (c *Conn) recvMsgs(ms []Message, flags int) (int, error) {
@@ -28,10 +27,7 @@ func (c *Conn) recvMsgs(ms []Message, flags int) (int, error) {
2827
var n int
2928
fn := func(s uintptr) bool {
3029
n, operr = recvmmsg(s, hs, flags)
31-
if operr == syscall.EAGAIN {
32-
return false
33-
}
34-
return true
30+
return ioComplete(flags, operr)
3531
}
3632
if err := c.c.Read(fn); err != nil {
3733
return n, err
@@ -60,10 +56,7 @@ func (c *Conn) sendMsgs(ms []Message, flags int) (int, error) {
6056
var n int
6157
fn := func(s uintptr) bool {
6258
n, operr = sendmmsg(s, hs, flags)
63-
if operr == syscall.EAGAIN {
64-
return false
65-
}
66-
return true
59+
return ioComplete(flags, operr)
6760
}
6861
if err := c.c.Write(fn); err != nil {
6962
return n, err

internal/socket/rawconn_msg.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package socket
99

1010
import (
1111
"os"
12-
"syscall"
1312
)
1413

1514
func (c *Conn) recvMsg(m *Message, flags int) error {
@@ -25,10 +24,7 @@ func (c *Conn) recvMsg(m *Message, flags int) error {
2524
var n int
2625
fn := func(s uintptr) bool {
2726
n, operr = recvmsg(s, &h, flags)
28-
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
29-
return false
30-
}
31-
return true
27+
return ioComplete(flags, operr)
3228
}
3329
if err := c.c.Read(fn); err != nil {
3430
return err
@@ -64,10 +60,7 @@ func (c *Conn) sendMsg(m *Message, flags int) error {
6460
var n int
6561
fn := func(s uintptr) bool {
6662
n, operr = sendmsg(s, &h, flags)
67-
if operr == syscall.EAGAIN || operr == syscall.EWOULDBLOCK {
68-
return false
69-
}
70-
return true
63+
return ioComplete(flags, operr)
7164
}
7265
if err := c.c.Write(fn); err != nil {
7366
return err
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2021 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.
4+
5+
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
6+
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
7+
8+
package socket_test
9+
10+
import (
11+
"bytes"
12+
"errors"
13+
"net"
14+
"runtime"
15+
"syscall"
16+
"testing"
17+
18+
"golang.org/x/net/internal/socket"
19+
"golang.org/x/net/nettest"
20+
)
21+
22+
func TestUDPDontwait(t *testing.T) {
23+
c, err := nettest.NewLocalPacketListener("udp")
24+
if err != nil {
25+
t.Skipf("not supported on %s/%s: %v", runtime.GOOS, runtime.GOARCH, err)
26+
}
27+
defer c.Close()
28+
cc, err := socket.NewConn(c.(*net.UDPConn))
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
isErrWouldblock := func(err error) bool {
33+
var errno syscall.Errno
34+
return errors.As(err, &errno) && (errno == syscall.EAGAIN || errno == syscall.EWOULDBLOCK)
35+
}
36+
37+
t.Run("Message-dontwait", func(t *testing.T) {
38+
// Read before something was sent; expect EWOULDBLOCK
39+
b := make([]byte, 32)
40+
rm := socket.Message{
41+
Buffers: [][]byte{b},
42+
}
43+
if err := cc.RecvMsg(&rm, syscall.MSG_DONTWAIT); !isErrWouldblock(err) {
44+
t.Fatal(err)
45+
}
46+
// To trigger EWOULDBLOCK by SendMsg, we have to send faster than what the
47+
// system/network is able to process. Whether or not we can trigger this
48+
// depends on the system, specifically on write buffer sizes and the speed
49+
// of the network interface.
50+
// We cannot expect to quickly and reliably trigger this, especially not
51+
// because this test sends data over a (fast) loopback. Consequently, we
52+
// only check that sending with MSG_DONTWAIT works at all and don't attempt
53+
// testing that we would eventually get EWOULDBLOCK here.
54+
data := []byte("HELLO-R-U-THERE")
55+
wm := socket.Message{
56+
Buffers: [][]byte{data},
57+
Addr: c.LocalAddr(),
58+
}
59+
// Send one message, repeat until we don't get EWOULDBLOCK. This will likely succeed at the first attempt.
60+
for {
61+
err := cc.SendMsg(&wm, syscall.MSG_DONTWAIT)
62+
if err == nil {
63+
break
64+
} else if !isErrWouldblock(err) {
65+
t.Fatal(err)
66+
}
67+
}
68+
// Read the message now available; again, this will likely succeed at the first attempt.
69+
for {
70+
err := cc.RecvMsg(&rm, syscall.MSG_DONTWAIT)
71+
if err == nil {
72+
break
73+
} else if !isErrWouldblock(err) {
74+
t.Fatal(err)
75+
}
76+
}
77+
if !bytes.Equal(b[:rm.N], data) {
78+
t.Fatalf("got %#v; want %#v", b[:rm.N], data)
79+
}
80+
})
81+
switch runtime.GOOS {
82+
case "android", "linux":
83+
t.Run("Messages", func(t *testing.T) {
84+
data := []byte("HELLO-R-U-THERE")
85+
wmbs := bytes.SplitAfter(data, []byte("-"))
86+
wms := []socket.Message{
87+
{Buffers: wmbs[:1], Addr: c.LocalAddr()},
88+
{Buffers: wmbs[1:], Addr: c.LocalAddr()},
89+
}
90+
b := make([]byte, 32)
91+
rmbs := [][][]byte{{b[:len(wmbs[0])]}, {b[len(wmbs[0]):]}}
92+
rms := []socket.Message{
93+
{Buffers: rmbs[0]},
94+
{Buffers: rmbs[1]},
95+
}
96+
_, err := cc.RecvMsgs(rms, syscall.MSG_DONTWAIT)
97+
if !isErrWouldblock(err) {
98+
t.Fatal(err)
99+
}
100+
for ntot := 0; ntot < len(wms); {
101+
n, err := cc.SendMsgs(wms[ntot:], syscall.MSG_DONTWAIT)
102+
if err == nil {
103+
ntot += n
104+
} else if !isErrWouldblock(err) {
105+
t.Fatal(err)
106+
}
107+
}
108+
for ntot := 0; ntot < len(rms); {
109+
n, err := cc.RecvMsgs(rms[ntot:], syscall.MSG_DONTWAIT)
110+
if err == nil {
111+
ntot += n
112+
} else if !isErrWouldblock(err) {
113+
t.Fatal(err)
114+
}
115+
}
116+
nn := 0
117+
for i := 0; i < len(rms); i++ {
118+
nn += rms[i].N
119+
}
120+
if !bytes.Equal(b[:nn], data) {
121+
t.Fatalf("got %#v; want %#v", b[:nn], data)
122+
}
123+
})
124+
}
125+
}

0 commit comments

Comments
 (0)