diff options
Diffstat (limited to 'src/pkg/net/dial_test.go')
-rw-r--r-- | src/pkg/net/dial_test.go | 178 |
1 files changed, 163 insertions, 15 deletions
diff --git a/src/pkg/net/dial_test.go b/src/pkg/net/dial_test.go index e24fecc8d..f1d813f41 100644 --- a/src/pkg/net/dial_test.go +++ b/src/pkg/net/dial_test.go @@ -5,13 +5,17 @@ package net import ( + "bytes" "flag" "fmt" "io" "os" + "os/exec" "reflect" "regexp" "runtime" + "strconv" + "sync" "testing" "time" ) @@ -137,7 +141,7 @@ func TestSelfConnect(t *testing.T) { n = 1000 } switch runtime.GOOS { - case "darwin", "freebsd", "netbsd", "openbsd", "plan9", "windows": + case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd", "plan9", "windows": // Non-Linux systems take a long time to figure // out that there is nothing listening on localhost. n = 100 @@ -314,6 +318,96 @@ func TestDialTimeoutFDLeak(t *testing.T) { } } +func numTCP() (ntcp, nopen, nclose int, err error) { + lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() + if err != nil { + return 0, 0, 0, err + } + ntcp += bytes.Count(lsof, []byte("TCP")) + for _, state := range []string{"LISTEN", "SYN_SENT", "SYN_RECEIVED", "ESTABLISHED"} { + nopen += bytes.Count(lsof, []byte(state)) + } + for _, state := range []string{"CLOSED", "CLOSE_WAIT", "LAST_ACK", "FIN_WAIT_1", "FIN_WAIT_2", "CLOSING", "TIME_WAIT"} { + nclose += bytes.Count(lsof, []byte(state)) + } + return ntcp, nopen, nclose, nil +} + +func TestDialMultiFDLeak(t *testing.T) { + if !supportsIPv4 || !supportsIPv6 { + t.Skip("neither ipv4 nor ipv6 is supported") + } + + halfDeadServer := func(dss *dualStackServer, ln Listener) { + for { + if c, err := ln.Accept(); err != nil { + return + } else { + // It just keeps established + // connections like a half-dead server + // does. + dss.putConn(c) + } + } + } + dss, err := newDualStackServer([]streamListener{ + {net: "tcp4", addr: "127.0.0.1"}, + {net: "tcp6", addr: "[::1]"}, + }) + if err != nil { + t.Fatalf("newDualStackServer failed: %v", err) + } + defer dss.teardown() + if err := dss.buildup(halfDeadServer); err != nil { + t.Fatalf("dualStackServer.buildup failed: %v", err) + } + + _, before, _, err := numTCP() + if err != nil { + t.Skipf("skipping test; error finding or running lsof: %v", err) + } + + var wg sync.WaitGroup + portnum, _, _ := dtoi(dss.port, 0) + ras := addrList{ + // Losers that will fail to connect, see RFC 6890. + &TCPAddr{IP: IPv4(198, 18, 0, 254), Port: portnum}, + &TCPAddr{IP: ParseIP("2001:2::254"), Port: portnum}, + + // Winner candidates of this race. + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum}, + &TCPAddr{IP: IPv6loopback, Port: portnum}, + + // Losers that will have established connections. + &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum}, + &TCPAddr{IP: IPv6loopback, Port: portnum}, + } + const T1 = 10 * time.Millisecond + const T2 = 2 * T1 + const N = 10 + for i := 0; i < N; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if c, err := dialMulti("tcp", "fast failover test", nil, ras, time.Now().Add(T1)); err == nil { + c.Close() + } + }() + } + wg.Wait() + time.Sleep(T2) + + ntcp, after, nclose, err := numTCP() + if err != nil { + t.Skipf("skipping test; error finding or running lsof: %v", err) + } + t.Logf("tcp sessions: %v, open sessions: %v, closing sessions: %v", ntcp, after, nclose) + + if after != before { + t.Fatalf("got %v open sessions; expected %v", after, before) + } +} + func numFD() int { if runtime.GOOS == "linux" { f, err := os.Open("/proc/self/fd") @@ -331,17 +425,22 @@ func numFD() int { panic("numFDs not implemented on " + runtime.GOOS) } -var testPoller = flag.Bool("poller", false, "platform supports runtime-integrated poller") - // Assert that a failed Dial attempt does not leak // runtime.PollDesc structures func TestDialFailPDLeak(t *testing.T) { - if !*testPoller { - t.Skip("test disabled; use -poller to enable") + if testing.Short() { + t.Skip("skipping test in short mode") + } + if runtime.GOOS == "windows" && runtime.GOARCH == "386" { + // Just skip the test because it takes too long. + t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH) } - const loops = 10 - const count = 20000 + maxprocs := runtime.GOMAXPROCS(0) + loops := 10 + maxprocs + // 500 is enough to turn over the chunk of pollcache. + // See allocPollDesc in runtime/netpoll.goc. + const count = 500 var old runtime.MemStats // used by sysdelta runtime.ReadMemStats(&old) sysdelta := func() uint64 { @@ -354,19 +453,26 @@ func TestDialFailPDLeak(t *testing.T) { d := &Dialer{Timeout: time.Nanosecond} // don't bother TCP with handshaking failcount := 0 for i := 0; i < loops; i++ { + var wg sync.WaitGroup for i := 0; i < count; i++ { - conn, err := d.Dial("tcp", "127.0.0.1:1") - if err == nil { - t.Error("dial should not succeed") - conn.Close() - t.FailNow() - } + wg.Add(1) + go func() { + defer wg.Done() + if c, err := d.Dial("tcp", "127.0.0.1:1"); err == nil { + t.Error("dial should not succeed") + c.Close() + } + }() + } + wg.Wait() + if t.Failed() { + t.FailNow() } if delta := sysdelta(); delta > 0 { failcount++ } // there are always some allocations on the first loop - if failcount > 3 { + if failcount > maxprocs+2 { t.Error("detected possible memory leak in runtime") t.FailNow() } @@ -381,7 +487,6 @@ func TestDialer(t *testing.T) { defer ln.Close() ch := make(chan error, 1) go func() { - var err error c, err := ln.Accept() if err != nil { ch <- fmt.Errorf("Accept failed: %v", err) @@ -407,3 +512,46 @@ func TestDialer(t *testing.T) { t.Error(err) } } + +func TestDialDualStackLocalhost(t *testing.T) { + if ips, err := LookupIP("localhost"); err != nil { + t.Fatalf("LookupIP failed: %v", err) + } else if len(ips) < 2 || !supportsIPv4 || !supportsIPv6 { + t.Skip("localhost doesn't have a pair of different address family IP addresses") + } + + touchAndByeServer := func(dss *dualStackServer, ln Listener) { + for { + if c, err := ln.Accept(); err != nil { + return + } else { + c.Close() + } + } + } + dss, err := newDualStackServer([]streamListener{ + {net: "tcp4", addr: "127.0.0.1"}, + {net: "tcp6", addr: "[::1]"}, + }) + if err != nil { + t.Fatalf("newDualStackServer failed: %v", err) + } + defer dss.teardown() + if err := dss.buildup(touchAndByeServer); err != nil { + t.Fatalf("dualStackServer.buildup failed: %v", err) + } + + d := &Dialer{DualStack: true} + for _ = range dss.lns { + if c, err := d.Dial("tcp", "localhost:"+dss.port); err != nil { + t.Errorf("Dial failed: %v", err) + } else { + if addr := c.LocalAddr().(*TCPAddr); addr.IP.To4() != nil { + dss.teardownNetwork("tcp4") + } else if addr.IP.To16() != nil && addr.IP.To4() == nil { + dss.teardownNetwork("tcp6") + } + c.Close() + } + } +} |