]> Cypherpunks repositories - gostls13.git/commitdiff
net: prevent spurious TCP connection setup notification on darwin
authorMikio Hara <mikioh.mikioh@gmail.com>
Wed, 9 Mar 2016 09:16:00 +0000 (18:16 +0900)
committerMikio Hara <mikioh.mikioh@gmail.com>
Wed, 16 Mar 2016 03:09:28 +0000 (03:09 +0000)
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 <mikioh.mikioh@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/net/error_unix_test.go
src/net/fd_unix.go
src/net/tcpsock_unix_test.go [new file with mode: 0644]

index db66d0acf13049df1e7be9450e76a259d668fd20..9ce9e12c5ecd0970bff09391ebadb64a97eebe5d 100644 (file)
@@ -6,7 +6,10 @@
 
 package net
 
-import "syscall"
+import (
+       "os"
+       "syscall"
+)
 
 var (
        errTimedout       = syscall.ETIMEDOUT
@@ -19,3 +22,13 @@ func isPlatformError(err error) bool {
        _, ok := err.(syscall.Errno)
        return ok
 }
+
+func samePlatformError(err, want error) bool {
+       if op, ok := err.(*OpError); ok {
+               err = op.Err
+       }
+       if sys, ok := err.(*os.SyscallError); ok {
+               err = sys.Err
+       }
+       return err == want
+}
index 6c385dbef2243bfa69e5708efaab6b58124db7a5..82590c27d00057e358525392a4d8454edbb9a086 100644 (file)
@@ -139,7 +139,16 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time, cancel <-c
                switch err := syscall.Errno(nerr); err {
                case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
                case syscall.Errno(0), syscall.EISCONN:
-                       return nil
+                       if runtime.GOOS != "darwin" {
+                               return nil
+                       }
+                       // See golang.org/issue/14548.
+                       // On Darwin, multiple connect system calls on
+                       // a non-blocking socket never harm SO_ERROR.
+                       switch err := connectFunc(fd.sysfd, ra); err {
+                       case nil, syscall.EISCONN:
+                               return nil
+                       }
                default:
                        return os.NewSyscallError("getsockopt", err)
                }
diff --git a/src/net/tcpsock_unix_test.go b/src/net/tcpsock_unix_test.go
new file mode 100644 (file)
index 0000000..c07f7d7
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright 2016 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.
+
+// +build darwin
+
+package net
+
+import (
+       "runtime"
+       "sync"
+       "syscall"
+       "testing"
+       "time"
+)
+
+// See golang.org/issue/14548.
+func TestTCPSupriousConnSetupCompletion(t *testing.T) {
+       if testing.Short() {
+               t.Skip("skipping in short mode")
+       }
+
+       ln, err := newLocalListener("tcp")
+       if err != nil {
+               t.Fatal(err)
+       }
+       var wg sync.WaitGroup
+       wg.Add(1)
+       go func(ln Listener) {
+               defer wg.Done()
+               for {
+                       c, err := ln.Accept()
+                       if err != nil {
+                               return
+                       }
+                       wg.Add(1)
+                       go func(c Conn) {
+                               var b [1]byte
+                               c.Read(b[:])
+                               c.Close()
+                               wg.Done()
+                       }(c)
+               }
+       }(ln)
+
+       attempts := int(1e4) // larger is better
+       wg.Add(attempts)
+       throttle := make(chan struct{}, runtime.GOMAXPROCS(-1)*2)
+       for i := 0; i < attempts; i++ {
+               throttle <- struct{}{}
+               go func(i int) {
+                       defer func() {
+                               <-throttle
+                               wg.Done()
+                       }()
+                       d := Dialer{Timeout: 50 * time.Millisecond}
+                       c, err := d.Dial(ln.Addr().Network(), ln.Addr().String())
+                       if err != nil {
+                               if perr := parseDialError(err); perr != nil {
+                                       t.Errorf("#%d: %v", i, err)
+                               }
+                               return
+                       }
+                       var b [1]byte
+                       if _, err := c.Write(b[:]); err != nil {
+                               if perr := parseWriteError(err); perr != nil {
+                                       t.Errorf("#%d: %v", i, err)
+                               }
+                               if samePlatformError(err, syscall.ENOTCONN) {
+                                       t.Errorf("#%d: %v", i, err)
+                               }
+                       }
+                       c.Close()
+               }(i)
+       }
+
+       ln.Close()
+       wg.Wait()
+}