Skip to content

Commit 24a83d3

Browse files
committed
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 <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 479c47e commit 24a83d3

12 files changed

+147
-28
lines changed

src/net/dial.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ type Dialer struct {
5757
// If zero, keep-alives are not enabled. Network protocols
5858
// that do not support keep-alives ignore this field.
5959
KeepAlive time.Duration
60+
61+
// Cancel is an optional channel whose closure indicates that
62+
// the dial should be canceled. Not all types of dials support
63+
// cancelation.
64+
Cancel <-chan struct{}
6065
}
6166

6267
// Return either now+Timeout or Deadline, whichever comes first.
@@ -361,7 +366,7 @@ func dialSingle(ctx *dialContext, ra Addr, deadline time.Time) (c Conn, err erro
361366
switch ra := ra.(type) {
362367
case *TCPAddr:
363368
la, _ := la.(*TCPAddr)
364-
c, err = testHookDialTCP(ctx.network, la, ra, deadline)
369+
c, err = testHookDialTCP(ctx.network, la, ra, deadline, ctx.Cancel)
365370
case *UDPAddr:
366371
la, _ := la.(*UDPAddr)
367372
c, err = dialUDP(ctx.network, la, ra, deadline)

src/net/dial_test.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package net
66

77
import (
8+
"internal/testenv"
89
"io"
910
"net/internal/socktest"
1011
"runtime"
@@ -236,8 +237,8 @@ const (
236237
// In some environments, the slow IPs may be explicitly unreachable, and fail
237238
// more quickly than expected. This test hook prevents dialTCP from returning
238239
// before the deadline.
239-
func slowDialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) {
240-
c, err := dialTCP(net, laddr, raddr, deadline)
240+
func slowDialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time, cancel <-chan struct{}) (*TCPConn, error) {
241+
c, err := dialTCP(net, laddr, raddr, deadline, cancel)
241242
if ParseIP(slowDst4).Equal(raddr.IP) || ParseIP(slowDst6).Equal(raddr.IP) {
242243
time.Sleep(deadline.Sub(time.Now()))
243244
}
@@ -716,3 +717,64 @@ func TestDialerKeepAlive(t *testing.T) {
716717
}
717718
}
718719
}
720+
721+
func TestDialCancel(t *testing.T) {
722+
if runtime.GOOS == "plan9" || runtime.GOOS == "nacl" {
723+
// plan9 is not implemented and nacl doesn't have
724+
// external network access.
725+
t.Skip("skipping on %s", runtime.GOOS)
726+
}
727+
onGoBuildFarm := testenv.Builder() != ""
728+
if testing.Short() && !onGoBuildFarm {
729+
t.Skip("skipping in short mode")
730+
}
731+
732+
blackholeIPPort := JoinHostPort(slowDst4, "1234")
733+
if !supportsIPv4 {
734+
blackholeIPPort = JoinHostPort(slowDst6, "1234")
735+
}
736+
737+
ticker := time.NewTicker(10 * time.Millisecond)
738+
defer ticker.Stop()
739+
740+
const cancelTick = 5 // the timer tick we cancel the dial at
741+
const timeoutTick = 100
742+
743+
var d Dialer
744+
cancel := make(chan struct{})
745+
d.Cancel = cancel
746+
errc := make(chan error, 1)
747+
connc := make(chan Conn, 1)
748+
go func() {
749+
if c, err := d.Dial("tcp", blackholeIPPort); err != nil {
750+
errc <- err
751+
} else {
752+
connc <- c
753+
}
754+
}()
755+
ticks := 0
756+
for {
757+
select {
758+
case <-ticker.C:
759+
ticks++
760+
if ticks == cancelTick {
761+
close(cancel)
762+
}
763+
if ticks == timeoutTick {
764+
t.Fatal("timeout waiting for dial to fail")
765+
}
766+
case c := <-connc:
767+
c.Close()
768+
t.Fatal("unexpected successful connection")
769+
case err := <-errc:
770+
if ticks < cancelTick {
771+
t.Fatalf("dial error after %d ticks (%d before cancel sent): %v",
772+
ticks, cancelTick-ticks, err)
773+
}
774+
if oe, ok := err.(*OpError); !ok || oe.Err != errCanceled {
775+
t.Fatalf("dial error = %v (%T); want OpError with Err == errCanceled", err, err)
776+
}
777+
return // success.
778+
}
779+
}
780+
}

src/net/fd_unix.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (fd *netFD) name() string {
6868
return fd.net + ":" + ls + "->" + rs
6969
}
7070

71-
func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error {
71+
func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time, cancel <-chan struct{}) error {
7272
// Do not need to call fd.writeLock here,
7373
// because fd is not yet accessible to user,
7474
// so no concurrent operations are possible.
@@ -102,6 +102,19 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error {
102102
fd.setWriteDeadline(deadline)
103103
defer fd.setWriteDeadline(noDeadline)
104104
}
105+
if cancel != nil {
106+
done := make(chan bool)
107+
defer close(done)
108+
go func() {
109+
select {
110+
case <-cancel:
111+
// Force the runtime's poller to immediately give
112+
// up waiting for writability.
113+
fd.setWriteDeadline(aLongTimeAgo)
114+
case <-done:
115+
}
116+
}()
117+
}
105118
for {
106119
// Performing multiple connect system calls on a
107120
// non-blocking socket under Unix variants does not
@@ -112,6 +125,11 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error {
112125
// succeeded or failed. See issue 7474 for further
113126
// details.
114127
if err := fd.pd.WaitWrite(); err != nil {
128+
select {
129+
case <-cancel:
130+
return errCanceled
131+
default:
132+
}
115133
return err
116134
}
117135
nerr, err := getsockoptIntFunc(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR)

src/net/fd_windows.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func (fd *netFD) setAddr(laddr, raddr Addr) {
320320
runtime.SetFinalizer(fd, (*netFD).Close)
321321
}
322322

323-
func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error {
323+
func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time, cancel <-chan struct{}) error {
324324
// Do not need to call fd.writeLock here,
325325
// because fd is not yet accessible to user,
326326
// so no concurrent operations are possible.
@@ -351,14 +351,38 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time) error {
351351
// Call ConnectEx API.
352352
o := &fd.wop
353353
o.sa = ra
354+
if cancel != nil {
355+
done := make(chan struct{})
356+
defer close(done)
357+
go func() {
358+
select {
359+
case <-cancel:
360+
// TODO(bradfitz,brainman): cancel the dial operation
361+
// somehow. Brad doesn't know Windows but is going to
362+
// try this:
363+
if canCancelIO {
364+
syscall.CancelIoEx(o.fd.sysfd, &o.o)
365+
} else {
366+
wsrv.req <- ioSrvReq{o, nil}
367+
<-o.errc
368+
}
369+
case <-done:
370+
}
371+
}()
372+
}
354373
_, err := wsrv.ExecIO(o, "ConnectEx", func(o *operation) error {
355374
return connectExFunc(o.fd.sysfd, o.sa, nil, 0, nil, &o.o)
356375
})
357376
if err != nil {
358-
if _, ok := err.(syscall.Errno); ok {
359-
err = os.NewSyscallError("connectex", err)
377+
select {
378+
case <-cancel:
379+
return errCanceled
380+
default:
381+
if _, ok := err.(syscall.Errno); ok {
382+
err = os.NewSyscallError("connectex", err)
383+
}
384+
return err
360385
}
361-
return err
362386
}
363387
// Refresh socket properties.
364388
return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd))))

src/net/iprawsock_posix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func dialIP(netProto string, laddr, raddr *IPAddr, deadline time.Time) (*IPConn,
220220
if raddr == nil {
221221
return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress}
222222
}
223-
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_RAW, proto, "dial")
223+
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_RAW, proto, "dial", noCancel)
224224
if err != nil {
225225
return nil, &OpError{Op: "dial", Net: netProto, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err}
226226
}
@@ -241,7 +241,7 @@ func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error) {
241241
default:
242242
return nil, &OpError{Op: "listen", Net: netProto, Source: nil, Addr: laddr.opAddr(), Err: UnknownNetworkError(netProto)}
243243
}
244-
fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_RAW, proto, "listen")
244+
fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_RAW, proto, "listen", noCancel)
245245
if err != nil {
246246
return nil, &OpError{Op: "listen", Net: netProto, Source: nil, Addr: laddr.opAddr(), Err: err}
247247
}

src/net/ipsock_posix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family
156156

157157
// Internet sockets (TCP, UDP, IP)
158158

159-
func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string) (fd *netFD, err error) {
159+
func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string, cancel <-chan struct{}) (fd *netFD, err error) {
160160
family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
161-
return socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline)
161+
return socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline, cancel)
162162
}
163163

164164
func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, error) {

src/net/net.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,16 @@ func (e *OpError) Error() string {
426426
return s
427427
}
428428

429-
var noDeadline = time.Time{}
429+
var (
430+
// aLongTimeAgo is a non-zero time, far in the past, used for
431+
// immediate cancelation of dials.
432+
aLongTimeAgo = time.Unix(233431200, 0)
433+
434+
// nonDeadline and noCancel are just zero values for
435+
// readability with functions taking too many parameters.
436+
noDeadline = time.Time{}
437+
noCancel = (chan struct{})(nil)
438+
)
430439

431440
type timeout interface {
432441
Timeout() bool

src/net/sock_posix.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type sockaddr interface {
3434

3535
// socket returns a network file descriptor that is ready for
3636
// asynchronous I/O using the network poller.
37-
func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, deadline time.Time) (fd *netFD, err error) {
37+
func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, deadline time.Time, cancel <-chan struct{}) (fd *netFD, err error) {
3838
s, err := sysSocket(family, sotype, proto)
3939
if err != nil {
4040
return nil, err
@@ -86,7 +86,7 @@ func socket(net string, family, sotype, proto int, ipv6only bool, laddr, raddr s
8686
return fd, nil
8787
}
8888
}
89-
if err := fd.dial(laddr, raddr, deadline); err != nil {
89+
if err := fd.dial(laddr, raddr, deadline, cancel); err != nil {
9090
fd.Close()
9191
return nil, err
9292
}
@@ -117,7 +117,7 @@ func (fd *netFD) addrFunc() func(syscall.Sockaddr) Addr {
117117
return func(syscall.Sockaddr) Addr { return nil }
118118
}
119119

120-
func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time) error {
120+
func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, cancel <-chan struct{}) error {
121121
var err error
122122
var lsa syscall.Sockaddr
123123
if laddr != nil {
@@ -134,7 +134,7 @@ func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time) error {
134134
if rsa, err = raddr.sockaddr(fd.family); err != nil {
135135
return err
136136
}
137-
if err := fd.connect(lsa, rsa, deadline); err != nil {
137+
if err := fd.connect(lsa, rsa, deadline, cancel); err != nil {
138138
return err
139139
}
140140
fd.isConnected = true

src/net/tcpsock_plan9.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,14 @@ func (c *TCPConn) SetNoDelay(noDelay bool) error {
107107
// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is
108108
// used as the local address for the connection.
109109
func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
110-
return dialTCP(net, laddr, raddr, noDeadline)
110+
return dialTCP(net, laddr, raddr, noDeadline, noCancel)
111111
}
112112

113-
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) {
113+
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time, cancel <-chan struct{}) (*TCPConn, error) {
114114
if !deadline.IsZero() {
115115
panic("net.dialTCP: deadline not implemented on Plan 9")
116116
}
117+
// TODO(bradfitz,0intro): also use the cancel channel.
117118
switch net {
118119
case "tcp", "tcp4", "tcp6":
119120
default:

src/net/tcpsock_posix.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
164164
if raddr == nil {
165165
return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress}
166166
}
167-
return dialTCP(net, laddr, raddr, noDeadline)
167+
return dialTCP(net, laddr, raddr, noDeadline, noCancel)
168168
}
169169

170-
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) {
171-
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial")
170+
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time, cancel <-chan struct{}) (*TCPConn, error) {
171+
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", cancel)
172172

173173
// TCP has a rarely used mechanism called a 'simultaneous connection' in
174174
// which Dial("tcp", addr1, addr2) run on the machine at addr1 can
@@ -198,7 +198,7 @@ func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, e
198198
if err == nil {
199199
fd.Close()
200200
}
201-
fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial")
201+
fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", cancel)
202202
}
203203

204204
if err != nil {
@@ -326,7 +326,7 @@ func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) {
326326
if laddr == nil {
327327
laddr = &TCPAddr{}
328328
}
329-
fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_STREAM, 0, "listen")
329+
fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_STREAM, 0, "listen", noCancel)
330330
if err != nil {
331331
return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err}
332332
}

src/net/udpsock_posix.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func DialUDP(net string, laddr, raddr *UDPAddr) (*UDPConn, error) {
189189
}
190190

191191
func dialUDP(net string, laddr, raddr *UDPAddr, deadline time.Time) (*UDPConn, error) {
192-
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial")
192+
fd, err := internetSocket(net, laddr, raddr, deadline, syscall.SOCK_DGRAM, 0, "dial", noCancel)
193193
if err != nil {
194194
return nil, &OpError{Op: "dial", Net: net, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err}
195195
}
@@ -212,7 +212,7 @@ func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error) {
212212
if laddr == nil {
213213
laddr = &UDPAddr{}
214214
}
215-
fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen")
215+
fd, err := internetSocket(net, laddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", noCancel)
216216
if err != nil {
217217
return nil, &OpError{Op: "listen", Net: net, Source: nil, Addr: laddr, Err: err}
218218
}
@@ -239,7 +239,7 @@ func ListenMulticastUDP(network string, ifi *Interface, gaddr *UDPAddr) (*UDPCon
239239
if gaddr == nil || gaddr.IP == nil {
240240
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr.opAddr(), Err: errMissingAddress}
241241
}
242-
fd, err := internetSocket(network, gaddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen")
242+
fd, err := internetSocket(network, gaddr, nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", noCancel)
243243
if err != nil {
244244
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: gaddr, Err: err}
245245
}

src/net/unixsock_posix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func unixSocket(net string, laddr, raddr sockaddr, mode string, deadline time.Ti
4242
return nil, errors.New("unknown mode: " + mode)
4343
}
4444

45-
fd, err := socket(net, syscall.AF_UNIX, sotype, 0, false, laddr, raddr, deadline)
45+
fd, err := socket(net, syscall.AF_UNIX, sotype, 0, false, laddr, raddr, deadline, noCancel)
4646
if err != nil {
4747
return nil, err
4848
}

0 commit comments

Comments
 (0)