aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/net/dial_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/net/dial_test.go')
-rw-r--r--libgo/go/net/dial_test.go488
1 files changed, 321 insertions, 167 deletions
diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go
index 2311b10..8b21e6b 100644
--- a/libgo/go/net/dial_test.go
+++ b/libgo/go/net/dial_test.go
@@ -5,6 +5,8 @@
package net
import (
+ "bufio"
+ "context"
"internal/testenv"
"io"
"net/internal/socktest"
@@ -23,13 +25,12 @@ var prohibitionaryDialArgTests = []struct {
}
func TestProhibitionaryDialArg(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
switch runtime.GOOS {
case "plan9":
t.Skipf("not supported on %s", runtime.GOOS)
}
- if testing.Short() || !*testExternal {
- t.Skip("avoid external network")
- }
if !supportsIPv4map {
t.Skip("mapping ipv4 address inside ipv6 address not supported")
}
@@ -54,56 +55,12 @@ func TestProhibitionaryDialArg(t *testing.T) {
}
}
-func TestSelfConnect(t *testing.T) {
- if runtime.GOOS == "windows" {
- // TODO(brainman): do not know why it hangs.
- t.Skip("known-broken test on windows")
- }
-
- // Test that Dial does not honor self-connects.
- // See the comment in DialTCP.
-
- // Find a port that would be used as a local address.
- l, err := Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- c, err := Dial("tcp", l.Addr().String())
- if err != nil {
- t.Fatal(err)
- }
- addr := c.LocalAddr().String()
- c.Close()
- l.Close()
-
- // Try to connect to that address repeatedly.
- n := 100000
- if testing.Short() {
- n = 1000
- }
- switch runtime.GOOS {
- case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd", "plan9", "solaris", "windows":
- // Non-Linux systems take a long time to figure
- // out that there is nothing listening on localhost.
- n = 100
- }
- for i := 0; i < n; i++ {
- c, err := DialTimeout("tcp", addr, time.Millisecond)
- if err == nil {
- if c.LocalAddr().String() == addr {
- t.Errorf("#%d: Dial %q self-connect", i, addr)
- } else {
- t.Logf("#%d: Dial %q succeeded - possibly racing with other listener", i, addr)
- }
- c.Close()
- }
- }
-}
-
func TestDialTimeoutFDLeak(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
+ case "openbsd":
+ testenv.SkipFlaky(t, 15157)
}
const T = 100 * time.Millisecond
@@ -130,17 +87,14 @@ func TestDialTimeoutFDLeak(t *testing.T) {
// socktest.Switch.
// It may happen when the Dial call bumps against TCP
// simultaneous open. See selfConnect in tcpsock_posix.go.
- defer func() {
- sw.Set(socktest.FilterClose, nil)
- forceCloseSockets()
- }()
+ defer func() { sw.Set(socktest.FilterClose, nil) }()
var mu sync.Mutex
var attempts int
sw.Set(socktest.FilterClose, func(so *socktest.Status) (socktest.AfterFilter, error) {
mu.Lock()
attempts++
mu.Unlock()
- return nil, errTimedout
+ return nil, nil
})
const N = 100
@@ -176,6 +130,12 @@ func TestDialerDualStackFDLeak(t *testing.T) {
t.Skip("both IPv4 and IPv6 are required")
}
+ closedPortDelay, expectClosedPortDelay := dialClosedPort()
+ if closedPortDelay > expectClosedPortDelay {
+ t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay)
+ }
+
+ before := sw.Sockets()
origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = lookupLocalhost
@@ -188,24 +148,19 @@ func TestDialerDualStackFDLeak(t *testing.T) {
c.Close()
}
}
- dss, err := newDualStackServer([]streamListener{
- {network: "tcp4", address: "127.0.0.1"},
- {network: "tcp6", address: "::1"},
- })
+ dss, err := newDualStackServer()
if err != nil {
t.Fatal(err)
}
- defer dss.teardown()
if err := dss.buildup(handler); err != nil {
+ dss.teardown()
t.Fatal(err)
}
- before := sw.Sockets()
- const T = 100 * time.Millisecond
const N = 10
var wg sync.WaitGroup
wg.Add(N)
- d := &Dialer{DualStack: true, Timeout: T}
+ d := &Dialer{DualStack: true, Timeout: 100*time.Millisecond + closedPortDelay}
for i := 0; i < N; i++ {
go func() {
defer wg.Done()
@@ -218,7 +173,7 @@ func TestDialerDualStackFDLeak(t *testing.T) {
}()
}
wg.Wait()
- time.Sleep(2 * T) // wait for the dial racers to stop
+ dss.teardown()
after := sw.Sockets()
if len(after) != len(before) {
t.Errorf("got %d; want %d", len(after), len(before))
@@ -229,18 +184,18 @@ func TestDialerDualStackFDLeak(t *testing.T) {
// expected to hang until the timeout elapses. These addresses are reserved
// for benchmarking by RFC 6890.
const (
- slowDst4 = "192.18.0.254"
- slowDst6 = "2001:2::254"
- slowTimeout = 1 * time.Second
+ slowDst4 = "198.18.0.254"
+ slowDst6 = "2001:2::254"
)
// In some environments, the slow IPs may be explicitly unreachable, and fail
// more quickly than expected. This test hook prevents dialTCP from returning
// before the deadline.
-func slowDialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time, cancel <-chan struct{}) (*TCPConn, error) {
- c, err := dialTCP(net, laddr, raddr, deadline, cancel)
+func slowDialTCP(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ c, err := doDialTCP(ctx, net, laddr, raddr)
if ParseIP(slowDst4).Equal(raddr.IP) || ParseIP(slowDst6).Equal(raddr.IP) {
- time.Sleep(deadline.Sub(time.Now()))
+ // Wait for the deadline, or indefinitely if none exists.
+ <-ctx.Done()
}
return c, err
}
@@ -278,9 +233,8 @@ func dialClosedPort() (actual, expected time.Duration) {
}
func TestDialParallel(t *testing.T) {
- if testing.Short() || !*testExternal {
- t.Skip("avoid external network")
- }
+ testenv.MustHaveExternalNetwork(t)
+
if !supportsIPv4 || !supportsIPv6 {
t.Skip("both IPv4 and IPv6 are required")
}
@@ -294,7 +248,7 @@ func TestDialParallel(t *testing.T) {
const fallbackDelay = 200 * time.Millisecond
// Some cases will run quickly when "connection refused" is fast,
- // or trigger the fallbackDelay on Windows. This value holds the
+ // or trigger the fallbackDelay on Windows. This value holds the
// lesser of the two delays.
var closedPortOrFallbackDelay time.Duration
if closedPortDelay < fallbackDelay {
@@ -369,10 +323,7 @@ func TestDialParallel(t *testing.T) {
}
for i, tt := range testCases {
- dss, err := newDualStackServer([]streamListener{
- {network: "tcp4", address: "127.0.0.1"},
- {network: "tcp6", address: "::1"},
- })
+ dss, err := newDualStackServer()
if err != nil {
t.Fatal(err)
}
@@ -389,17 +340,15 @@ func TestDialParallel(t *testing.T) {
fallbacks := makeAddrs(tt.fallbacks, dss.port)
d := Dialer{
FallbackDelay: fallbackDelay,
- Timeout: slowTimeout,
- }
- ctx := &dialContext{
- Dialer: d,
- network: "tcp",
- address: "?",
- finalDeadline: d.deadline(time.Now()),
}
startTime := time.Now()
- c, err := dialParallel(ctx, primaries, fallbacks)
- elapsed := time.Now().Sub(startTime)
+ dp := &dialParam{
+ Dialer: d,
+ network: "tcp",
+ address: "?",
+ }
+ c, err := dialParallel(context.Background(), dp, primaries, fallbacks)
+ elapsed := time.Since(startTime)
if c != nil {
c.Close()
@@ -418,12 +367,30 @@ func TestDialParallel(t *testing.T) {
} else if !(elapsed <= expectElapsedMax) {
t.Errorf("#%d: got %v; want <= %v", i, elapsed, expectElapsedMax)
}
+
+ // Repeat each case, ensuring that it can be canceled quickly.
+ ctx, cancel := context.WithCancel(context.Background())
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ time.Sleep(5 * time.Millisecond)
+ cancel()
+ wg.Done()
+ }()
+ startTime = time.Now()
+ c, err = dialParallel(ctx, dp, primaries, fallbacks)
+ if c != nil {
+ c.Close()
+ }
+ elapsed = time.Now().Sub(startTime)
+ if elapsed > 100*time.Millisecond {
+ t.Errorf("#%d (cancel): got %v; want <= 100ms", i, elapsed)
+ }
+ wg.Wait()
}
- // Wait for any slowDst4/slowDst6 connections to timeout.
- time.Sleep(slowTimeout * 3 / 2)
}
-func lookupSlowFast(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, error) {
+func lookupSlowFast(ctx context.Context, fn func(context.Context, string) ([]IPAddr, error), host string) ([]IPAddr, error) {
switch host {
case "slow6loopback4":
// Returns a slow IPv6 address, and a local IPv4 address.
@@ -432,14 +399,13 @@ func lookupSlowFast(fn func(string) ([]IPAddr, error), host string) ([]IPAddr, e
{IP: ParseIP("127.0.0.1")},
}, nil
default:
- return fn(host)
+ return fn(ctx, host)
}
}
func TestDialerFallbackDelay(t *testing.T) {
- if testing.Short() || !*testExternal {
- t.Skip("avoid external network")
- }
+ testenv.MustHaveExternalNetwork(t)
+
if !supportsIPv4 || !supportsIPv6 {
t.Skip("both IPv4 and IPv6 are required")
}
@@ -463,8 +429,6 @@ func TestDialerFallbackDelay(t *testing.T) {
{true, 200 * time.Millisecond, 200 * time.Millisecond},
// The default is 300ms.
{true, 0, 300 * time.Millisecond},
- // This case is last, in order to wait for hanging slowDst6 connections.
- {false, 0, slowTimeout},
}
handler := func(dss *dualStackServer, ln Listener) {
@@ -476,9 +440,7 @@ func TestDialerFallbackDelay(t *testing.T) {
c.Close()
}
}
- dss, err := newDualStackServer([]streamListener{
- {network: "tcp", address: "127.0.0.1"},
- })
+ dss, err := newDualStackServer()
if err != nil {
t.Fatal(err)
}
@@ -488,7 +450,7 @@ func TestDialerFallbackDelay(t *testing.T) {
}
for i, tt := range testCases {
- d := &Dialer{DualStack: tt.dualstack, FallbackDelay: tt.delay, Timeout: slowTimeout}
+ d := &Dialer{DualStack: tt.dualstack, FallbackDelay: tt.delay}
startTime := time.Now()
c, err := d.Dial("tcp", JoinHostPort("slow6loopback4", dss.port))
@@ -509,46 +471,78 @@ func TestDialerFallbackDelay(t *testing.T) {
}
}
-func TestDialSerialAsyncSpuriousConnection(t *testing.T) {
- if runtime.GOOS == "plan9" {
- t.Skip("skipping on plan9; no deadline support, golang.org/issue/11932")
+func TestDialParallelSpuriousConnection(t *testing.T) {
+ if !supportsIPv4 || !supportsIPv6 {
+ t.Skip("both IPv4 and IPv6 are required")
}
- ln, err := newLocalListener("tcp")
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+ handler := func(dss *dualStackServer, ln Listener) {
+ // Accept one connection per address.
+ c, err := ln.Accept()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // The client should close itself, without sending data.
+ c.SetReadDeadline(time.Now().Add(1 * time.Second))
+ var b [1]byte
+ if _, err := c.Read(b[:]); err != io.EOF {
+ t.Errorf("got %v; want %v", err, io.EOF)
+ }
+ c.Close()
+ wg.Done()
+ }
+ dss, err := newDualStackServer()
if err != nil {
t.Fatal(err)
}
- defer ln.Close()
+ defer dss.teardown()
+ if err := dss.buildup(handler); err != nil {
+ t.Fatal(err)
+ }
+
+ const fallbackDelay = 100 * time.Millisecond
+
+ origTestHookDialTCP := testHookDialTCP
+ defer func() { testHookDialTCP = origTestHookDialTCP }()
+ testHookDialTCP = func(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ // Sleep long enough for Happy Eyeballs to kick in, and inhibit cancelation.
+ // This forces dialParallel to juggle two successful connections.
+ time.Sleep(fallbackDelay * 2)
- d := Dialer{}
- ctx := &dialContext{
- Dialer: d,
- network: "tcp",
- address: "?",
- finalDeadline: d.deadline(time.Now()),
+ // Now ignore the provided context (which will be canceled) and use a
+ // different one to make sure this completes with a valid connection,
+ // which we hope to be closed below:
+ return doDialTCP(context.Background(), net, laddr, raddr)
}
- results := make(chan dialResult)
- cancel := make(chan struct{})
+ d := Dialer{
+ FallbackDelay: fallbackDelay,
+ }
+ dp := &dialParam{
+ Dialer: d,
+ network: "tcp",
+ address: "?",
+ }
- // Spawn a connection in the background.
- go dialSerialAsync(ctx, addrList{ln.Addr()}, nil, cancel, results)
+ makeAddr := func(ip string) addrList {
+ addr, err := ResolveTCPAddr("tcp", JoinHostPort(ip, dss.port))
+ if err != nil {
+ t.Fatal(err)
+ }
+ return addrList{addr}
+ }
- // Receive it at the server.
- c, err := ln.Accept()
+ // dialParallel returns one connection (and closes the other.)
+ c, err := dialParallel(context.Background(), dp, makeAddr("127.0.0.1"), makeAddr("::1"))
if err != nil {
t.Fatal(err)
}
- defer c.Close()
-
- // Tell dialSerialAsync that someone else won the race.
- close(cancel)
+ c.Close()
- // The connection should close itself, without sending data.
- c.SetReadDeadline(time.Now().Add(1 * time.Second))
- var b [1]byte
- if _, err := c.Read(b[:]); err != io.EOF {
- t.Errorf("got %v; want %v", err, io.EOF)
- }
+ // The server should've seen both connections.
+ wg.Wait()
}
func TestDialerPartialDeadline(t *testing.T) {
@@ -587,44 +581,125 @@ func TestDialerPartialDeadline(t *testing.T) {
}
func TestDialerLocalAddr(t *testing.T) {
- ch := make(chan error, 1)
- handler := func(ls *localServer, ln Listener) {
- c, err := ln.Accept()
- if err != nil {
- ch <- err
- return
- }
- defer c.Close()
- ch <- nil
- }
- ls, err := newLocalServer("tcp")
- if err != nil {
- t.Fatal(err)
+ if !supportsIPv4 || !supportsIPv6 {
+ t.Skip("both IPv4 and IPv6 are required")
}
- defer ls.teardown()
- if err := ls.buildup(handler); err != nil {
- t.Fatal(err)
+
+ type test struct {
+ network, raddr string
+ laddr Addr
+ error
+ }
+ var tests = []test{
+ {"tcp4", "127.0.0.1", nil, nil},
+ {"tcp4", "127.0.0.1", &TCPAddr{}, nil},
+ {"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
+ {"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
+ {"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("::")}, &AddrError{Err: "some error"}},
+ {"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, nil},
+ {"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, nil},
+ {"tcp4", "127.0.0.1", &TCPAddr{IP: IPv6loopback}, errNoSuitableAddress},
+ {"tcp4", "127.0.0.1", &UDPAddr{}, &AddrError{Err: "some error"}},
+ {"tcp4", "127.0.0.1", &UnixAddr{}, &AddrError{Err: "some error"}},
+
+ {"tcp6", "::1", nil, nil},
+ {"tcp6", "::1", &TCPAddr{}, nil},
+ {"tcp6", "::1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
+ {"tcp6", "::1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
+ {"tcp6", "::1", &TCPAddr{IP: ParseIP("::")}, nil},
+ {"tcp6", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, errNoSuitableAddress},
+ {"tcp6", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, errNoSuitableAddress},
+ {"tcp6", "::1", &TCPAddr{IP: IPv6loopback}, nil},
+ {"tcp6", "::1", &UDPAddr{}, &AddrError{Err: "some error"}},
+ {"tcp6", "::1", &UnixAddr{}, &AddrError{Err: "some error"}},
+
+ {"tcp", "127.0.0.1", nil, nil},
+ {"tcp", "127.0.0.1", &TCPAddr{}, nil},
+ {"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
+ {"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
+ {"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, nil},
+ {"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, nil},
+ {"tcp", "127.0.0.1", &TCPAddr{IP: IPv6loopback}, errNoSuitableAddress},
+ {"tcp", "127.0.0.1", &UDPAddr{}, &AddrError{Err: "some error"}},
+ {"tcp", "127.0.0.1", &UnixAddr{}, &AddrError{Err: "some error"}},
+
+ {"tcp", "::1", nil, nil},
+ {"tcp", "::1", &TCPAddr{}, nil},
+ {"tcp", "::1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
+ {"tcp", "::1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
+ {"tcp", "::1", &TCPAddr{IP: ParseIP("::")}, nil},
+ {"tcp", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, errNoSuitableAddress},
+ {"tcp", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, errNoSuitableAddress},
+ {"tcp", "::1", &TCPAddr{IP: IPv6loopback}, nil},
+ {"tcp", "::1", &UDPAddr{}, &AddrError{Err: "some error"}},
+ {"tcp", "::1", &UnixAddr{}, &AddrError{Err: "some error"}},
+ }
+
+ if supportsIPv4map {
+ tests = append(tests, test{
+ "tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("::")}, nil,
+ })
+ } else {
+ tests = append(tests, test{
+ "tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("::")}, &AddrError{Err: "some error"},
+ })
}
- laddr, err := ResolveTCPAddr(ls.Listener.Addr().Network(), ls.Listener.Addr().String())
- if err != nil {
- t.Fatal(err)
+ origTestHookLookupIP := testHookLookupIP
+ defer func() { testHookLookupIP = origTestHookLookupIP }()
+ testHookLookupIP = lookupLocalhost
+ handler := func(ls *localServer, ln Listener) {
+ for {
+ c, err := ln.Accept()
+ if err != nil {
+ return
+ }
+ c.Close()
+ }
}
- laddr.Port = 0
- d := &Dialer{LocalAddr: laddr}
- c, err := d.Dial(ls.Listener.Addr().Network(), ls.Addr().String())
- if err != nil {
- t.Fatal(err)
+ var err error
+ var lss [2]*localServer
+ for i, network := range []string{"tcp4", "tcp6"} {
+ lss[i], err = newLocalServer(network)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer lss[i].teardown()
+ if err := lss[i].buildup(handler); err != nil {
+ t.Fatal(err)
+ }
}
- defer c.Close()
- c.Read(make([]byte, 1))
- err = <-ch
- if err != nil {
- t.Error(err)
+
+ for _, tt := range tests {
+ d := &Dialer{LocalAddr: tt.laddr}
+ var addr string
+ ip := ParseIP(tt.raddr)
+ if ip.To4() != nil {
+ addr = lss[0].Listener.Addr().String()
+ }
+ if ip.To16() != nil && ip.To4() == nil {
+ addr = lss[1].Listener.Addr().String()
+ }
+ c, err := d.Dial(tt.network, addr)
+ if err == nil && tt.error != nil || err != nil && tt.error == nil {
+ t.Errorf("%s %v->%s: got %v; want %v", tt.network, tt.laddr, tt.raddr, err, tt.error)
+ }
+ if err != nil {
+ if perr := parseDialError(err); perr != nil {
+ t.Error(perr)
+ }
+ continue
+ }
+ c.Close()
}
}
func TestDialerDualStack(t *testing.T) {
+ // This test is known to be flaky. Don't frighten regular
+ // users about it; only fail on the build dashboard.
+ if testenv.Builder() == "" {
+ testenv.SkipFlaky(t, 13324)
+ }
if !supportsIPv4 || !supportsIPv6 {
t.Skip("both IPv4 and IPv6 are required")
}
@@ -649,10 +724,7 @@ func TestDialerDualStack(t *testing.T) {
var timeout = 150*time.Millisecond + closedPortDelay
for _, dualstack := range []bool{false, true} {
- dss, err := newDualStackServer([]streamListener{
- {network: "tcp4", address: "127.0.0.1"},
- {network: "tcp6", address: "::1"},
- })
+ dss, err := newDualStackServer()
if err != nil {
t.Fatal(err)
}
@@ -677,7 +749,6 @@ func TestDialerDualStack(t *testing.T) {
c.Close()
}
}
- time.Sleep(timeout * 3 / 2) // wait for the dial racers to stop
}
func TestDialerKeepAlive(t *testing.T) {
@@ -719,14 +790,16 @@ func TestDialerKeepAlive(t *testing.T) {
}
func TestDialCancel(t *testing.T) {
- if runtime.GOOS == "plan9" || runtime.GOOS == "nacl" {
- // plan9 is not implemented and nacl doesn't have
- // external network access.
- t.Skipf("skipping on %s", runtime.GOOS)
+ switch testenv.Builder() {
+ case "linux-arm64-buildlet":
+ t.Skip("skipping on linux-arm64-buildlet; incompatible network config? issue 15191")
+ case "":
+ testenv.MustHaveExternalNetwork(t)
}
- onGoBuildFarm := testenv.Builder() != ""
- if testing.Short() && !onGoBuildFarm {
- t.Skip("skipping in short mode")
+
+ if runtime.GOOS == "nacl" {
+ // nacl doesn't have external network access.
+ t.Skipf("skipping on %s", runtime.GOOS)
}
blackholeIPPort := JoinHostPort(slowDst4, "1234")
@@ -781,3 +854,84 @@ func TestDialCancel(t *testing.T) {
}
}
}
+
+func TestCancelAfterDial(t *testing.T) {
+ if testing.Short() {
+ t.Skip("avoiding time.Sleep")
+ }
+
+ ln, err := newLocalListener("tcp")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ defer func() {
+ ln.Close()
+ wg.Wait()
+ }()
+
+ // Echo back the first line of each incoming connection.
+ go func() {
+ for {
+ c, err := ln.Accept()
+ if err != nil {
+ break
+ }
+ rb := bufio.NewReader(c)
+ line, err := rb.ReadString('\n')
+ if err != nil {
+ t.Error(err)
+ c.Close()
+ continue
+ }
+ if _, err := c.Write([]byte(line)); err != nil {
+ t.Error(err)
+ }
+ c.Close()
+ }
+ wg.Done()
+ }()
+
+ try := func() {
+ cancel := make(chan struct{})
+ d := &Dialer{Cancel: cancel}
+ c, err := d.Dial("tcp", ln.Addr().String())
+
+ // Immediately after dialing, request cancelation and sleep.
+ // Before Issue 15078 was fixed, this would cause subsequent operations
+ // to fail with an i/o timeout roughly 50% of the time.
+ close(cancel)
+ time.Sleep(10 * time.Millisecond)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Close()
+
+ // Send some data to confirm that the connection is still alive.
+ const message = "echo!\n"
+ if _, err := c.Write([]byte(message)); err != nil {
+ t.Fatal(err)
+ }
+
+ // The server should echo the line, and close the connection.
+ rb := bufio.NewReader(c)
+ line, err := rb.ReadString('\n')
+ if err != nil {
+ t.Fatal(err)
+ }
+ if line != message {
+ t.Errorf("got %q; want %q", line, message)
+ }
+ if _, err := rb.ReadByte(); err != io.EOF {
+ t.Errorf("got %v; want %v", err, io.EOF)
+ }
+ }
+
+ // This bug manifested about 50% of the time, so try it a few times.
+ for i := 0; i < 10; i++ {
+ try()
+ }
+}