net: add Dialer.Cancel to cancel pending dials

Dialer.Cancel is a new optional <-chan struct{} channel whose closure
indicates that the dial should be canceled. It is compatible with the
x/net/context and http.Request.Cancel types.

Tested by hand with:

package main

    import (
            "log"
            "net"
            "time"
    )

    func main() {
            log.Printf("start.")
            var d net.Dialer
            cancel := make(chan struct{})
            time.AfterFunc(2*time.Second, func() {
                    log.Printf("timeout firing")
                    close(cancel)
            })
            d.Cancel = cancel
            c, err := d.Dial("tcp", "192.168.0.1:22")
            if err != nil {
                    log.Print(err)
                    return
            }
            log.Fatalf("unexpected connect: %v", c)
    }

Which says:

    2015/12/14 22:24:58 start.
    2015/12/14 22:25:00 timeout firing
    2015/12/14 22:25:00 dial tcp 192.168.0.1:22: operation was canceled

Fixes #11225

Change-Id: I2ef39e3a540e29fe6bfec03ab7a629a6b187fcb3
Reviewed-on: https://go-review.googlesource.com/17821
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Brad Fitzpatrick 2015-12-14 22:21:48 +00:00
parent 479c47e478
commit 24a83d3545
12 changed files with 147 additions and 28 deletions

View file

@ -5,6 +5,7 @@
package net
import (
"internal/testenv"
"io"
"net/internal/socktest"
"runtime"
@ -236,8 +237,8 @@ const (
// 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) (*TCPConn, error) {
c, err := dialTCP(net, laddr, raddr, deadline)
func slowDialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time, cancel <-chan struct{}) (*TCPConn, error) {
c, err := dialTCP(net, laddr, raddr, deadline, cancel)
if ParseIP(slowDst4).Equal(raddr.IP) || ParseIP(slowDst6).Equal(raddr.IP) {
time.Sleep(deadline.Sub(time.Now()))
}
@ -716,3 +717,64 @@ 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.Skip("skipping on %s", runtime.GOOS)
}
onGoBuildFarm := testenv.Builder() != ""
if testing.Short() && !onGoBuildFarm {
t.Skip("skipping in short mode")
}
blackholeIPPort := JoinHostPort(slowDst4, "1234")
if !supportsIPv4 {
blackholeIPPort = JoinHostPort(slowDst6, "1234")
}
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
const cancelTick = 5 // the timer tick we cancel the dial at
const timeoutTick = 100
var d Dialer
cancel := make(chan struct{})
d.Cancel = cancel
errc := make(chan error, 1)
connc := make(chan Conn, 1)
go func() {
if c, err := d.Dial("tcp", blackholeIPPort); err != nil {
errc <- err
} else {
connc <- c
}
}()
ticks := 0
for {
select {
case <-ticker.C:
ticks++
if ticks == cancelTick {
close(cancel)
}
if ticks == timeoutTick {
t.Fatal("timeout waiting for dial to fail")
}
case c := <-connc:
c.Close()
t.Fatal("unexpected successful connection")
case err := <-errc:
if ticks < cancelTick {
t.Fatalf("dial error after %d ticks (%d before cancel sent): %v",
ticks, cancelTick-ticks, err)
}
if oe, ok := err.(*OpError); !ok || oe.Err != errCanceled {
t.Fatalf("dial error = %v (%T); want OpError with Err == errCanceled", err, err)
}
return // success.
}
}
}