Skip to content

Commit 76b724c

Browse files
committed
net: prevent spurious TCP connection setup notification on darwin
On the latest darwin kernels, kevent in runtime-integrated network poller sometimes reports SYN-SENT state sockets as ESTABLISHED ones, though it's still unclear what's the root cause. This change prevents such spurious notifications by additional connect system calls. Fixes #14548. Change-Id: Ie29788e38ca735ca77259befeba3229d6a30ac52 Reviewed-on: https://go-review.googlesource.com/20468 Run-TryBot: Mikio Hara <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 5ce0170 commit 76b724c

File tree

3 files changed

+103
-2
lines changed

3 files changed

+103
-2
lines changed

src/net/error_unix_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
package net
88

9-
import "syscall"
9+
import (
10+
"os"
11+
"syscall"
12+
)
1013

1114
var (
1215
errTimedout = syscall.ETIMEDOUT
@@ -19,3 +22,13 @@ func isPlatformError(err error) bool {
1922
_, ok := err.(syscall.Errno)
2023
return ok
2124
}
25+
26+
func samePlatformError(err, want error) bool {
27+
if op, ok := err.(*OpError); ok {
28+
err = op.Err
29+
}
30+
if sys, ok := err.(*os.SyscallError); ok {
31+
err = sys.Err
32+
}
33+
return err == want
34+
}

src/net/fd_unix.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,16 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time, cancel <-c
139139
switch err := syscall.Errno(nerr); err {
140140
case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
141141
case syscall.Errno(0), syscall.EISCONN:
142-
return nil
142+
if runtime.GOOS != "darwin" {
143+
return nil
144+
}
145+
// See golang.org/issue/14548.
146+
// On Darwin, multiple connect system calls on
147+
// a non-blocking socket never harm SO_ERROR.
148+
switch err := connectFunc(fd.sysfd, ra); err {
149+
case nil, syscall.EISCONN:
150+
return nil
151+
}
143152
default:
144153
return os.NewSyscallError("getsockopt", err)
145154
}

src/net/tcpsock_unix_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2016 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+
// +build darwin
6+
7+
package net
8+
9+
import (
10+
"runtime"
11+
"sync"
12+
"syscall"
13+
"testing"
14+
"time"
15+
)
16+
17+
// See golang.org/issue/14548.
18+
func TestTCPSupriousConnSetupCompletion(t *testing.T) {
19+
if testing.Short() {
20+
t.Skip("skipping in short mode")
21+
}
22+
23+
ln, err := newLocalListener("tcp")
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
var wg sync.WaitGroup
28+
wg.Add(1)
29+
go func(ln Listener) {
30+
defer wg.Done()
31+
for {
32+
c, err := ln.Accept()
33+
if err != nil {
34+
return
35+
}
36+
wg.Add(1)
37+
go func(c Conn) {
38+
var b [1]byte
39+
c.Read(b[:])
40+
c.Close()
41+
wg.Done()
42+
}(c)
43+
}
44+
}(ln)
45+
46+
attempts := int(1e4) // larger is better
47+
wg.Add(attempts)
48+
throttle := make(chan struct{}, runtime.GOMAXPROCS(-1)*2)
49+
for i := 0; i < attempts; i++ {
50+
throttle <- struct{}{}
51+
go func(i int) {
52+
defer func() {
53+
<-throttle
54+
wg.Done()
55+
}()
56+
d := Dialer{Timeout: 50 * time.Millisecond}
57+
c, err := d.Dial(ln.Addr().Network(), ln.Addr().String())
58+
if err != nil {
59+
if perr := parseDialError(err); perr != nil {
60+
t.Errorf("#%d: %v", i, err)
61+
}
62+
return
63+
}
64+
var b [1]byte
65+
if _, err := c.Write(b[:]); err != nil {
66+
if perr := parseWriteError(err); perr != nil {
67+
t.Errorf("#%d: %v", i, err)
68+
}
69+
if samePlatformError(err, syscall.ENOTCONN) {
70+
t.Errorf("#%d: %v", i, err)
71+
}
72+
}
73+
c.Close()
74+
}(i)
75+
}
76+
77+
ln.Close()
78+
wg.Wait()
79+
}

0 commit comments

Comments
 (0)