From 751a24e86e0044b75c075c21b13dce2db9a1f744 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 2 Apr 2013 13:24:16 -0700 Subject: [PATCH] net: delete DialOpt and DialOption; add struct Dialer Per discussions on golang-nuts and golang-dev: "Some concerns with DialOpt" https://groups.google.com/d/msg/golang-nuts/Hfh9aqhXyUw/W3uYi8lOdKcJ https://groups.google.com/d/msg/golang-dev/37omSQeWv4Y/KASGIfPpXh0J R=golang-dev, google, r CC=golang-dev https://golang.org/cl/8274043 --- doc/go1.1.html | 13 +-- src/pkg/net/dial.go | 237 ++++++++++---------------------------- src/pkg/net/dial_gen.go | 61 ++++++++++ src/pkg/net/fd_plan9.go | 4 +- src/pkg/net/fd_unix.go | 5 +- src/pkg/net/fd_windows.go | 7 +- 6 files changed, 132 insertions(+), 195 deletions(-) create mode 100644 src/pkg/net/dial_gen.go diff --git a/doc/go1.1.html b/doc/go1.1.html index 75eb02d45c..e2db58adcc 100644 --- a/doc/go1.1.html +++ b/doc/go1.1.html @@ -779,16 +779,9 @@ Since this API change fixes a bug, it is permitted by the Go 1 compatibility rul
  • -The net package includes a new function, -DialOpt, to supply options to -Dial. -Each option is represented by a new -DialOption interface. -The new functions -Deadline, -Timeout, -Network, and -LocalAddress return a DialOption. +The net package includes a new type, +Dialer, to supply options to +Dial.
  • diff --git a/src/pkg/net/dial.go b/src/pkg/net/dial.go index da5f7e3020..b18d283626 100644 --- a/src/pkg/net/dial.go +++ b/src/pkg/net/dial.go @@ -9,112 +9,48 @@ import ( "time" ) -// A DialOption modifies a DialOpt call. -type DialOption interface { - setDialOpt(*dialOpts) -} - -var noLocalAddr Addr // nil - -// dialOpts holds all the dial options, populated by a DialOption's -// setDialOpt. +// A Dialer contains options for connecting to an address. // -// All fields may be their zero value. -type dialOpts struct { - deadline time.Time - localAddr Addr - network string // if empty, "tcp" - deferredConnect bool -} - -func (o *dialOpts) net() string { - if o.network == "" { - return "tcp" +// The zero value for each field is equivalent to dialing +// without that option. Dialing with the zero value of Dialer +// is therefore equivalent to just calling the Dial function. +type Dialer struct { + // Timeout is the maximum amount of time a dial will wait for + // a connect to complete. If Deadline is also set, it may fail + // earlier. + // + // The default is no timeout. + // + // With or without a timeout, the operating system may impose + // its own earlier timeout. For instance, TCP timeouts are + // often around 3 minutes. + Timeout time.Duration + + // Deadline is the absolute point in time after which dials + // will fail. If Timeout is set, it may fail earlier. + // Zero means no deadline, or dependent on the operating system + // as with the Timeout option. + Deadline time.Time + + // LocalAddr is the local address to use when dialing an + // address. The address must be of a compatible type for the + // network being dialed. + // If nil, a local address is automatically chosen. + LocalAddr Addr +} + +// Return either now+Timeout or Deadline, whichever comes first. +// Or zero, if neither is set. +func (d *Dialer) deadline() time.Time { + if d.Timeout == 0 { + return d.Deadline + } + timeoutDeadline := time.Now().Add(d.Timeout) + if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) { + return timeoutDeadline + } else { + return d.Deadline } - return o.network -} - -var ( - // TCP is a dial option to dial with TCP (over IPv4 or IPv6). - TCP = Network("tcp") - - // UDP is a dial option to dial with UDP (over IPv4 or IPv6). - UDP = Network("udp") -) - -// Network returns a DialOption to dial using the given network. -// -// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), -// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" -// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and -// "unixpacket". -// -// For IP networks, net must be "ip", "ip4" or "ip6" followed -// by a colon and a protocol number or name, such as -// "ipv4:1" or "ip6:ospf". -func Network(net string) DialOption { - return dialNetwork(net) -} - -type dialNetwork string - -func (s dialNetwork) setDialOpt(o *dialOpts) { - o.network = string(s) -} - -// Deadline returns a DialOption to fail a dial that doesn't -// complete before t. -func Deadline(t time.Time) DialOption { - return dialDeadline(t) -} - -type dialDeadline time.Time - -func (t dialDeadline) setDialOpt(o *dialOpts) { - o.deadline = time.Time(t) -} - -// Timeout returns a DialOption to fail a dial that doesn't -// complete within the provided duration. -func Timeout(d time.Duration) DialOption { - return dialTimeoutOpt(d) -} - -type dialTimeoutOpt time.Duration - -func (d dialTimeoutOpt) setDialOpt(o *dialOpts) { - o.deadline = time.Now().Add(time.Duration(d)) -} - -type tcpFastOpen struct{} - -func (tcpFastOpen) setDialOpt(o *dialOpts) { - o.deferredConnect = true -} - -// TODO(bradfitz): implement this (golang.org/issue/4842) and unexport this. -// -// TCPFastTimeout returns an option to use TCP Fast Open (TFO) when -// doing this dial. It is only valid for use with TCP connections. -// Data sent over a TFO connection may be processed by the peer -// multiple times, so should be used with caution. -func todo_TCPFastTimeout() DialOption { - return tcpFastOpen{} -} - -type localAddrOption struct { - la Addr -} - -func (a localAddrOption) setDialOpt(o *dialOpts) { - o.localAddr = a.la -} - -// LocalAddress returns a dial option to perform a dial with the -// provided local address. The address must be of a compatible type -// for the network being dialed. -func LocalAddress(addr Addr) DialOption { - return localAddrOption{addr} } func parseNetwork(net string) (afnet string, proto int, err error) { @@ -161,7 +97,7 @@ func resolveAddr(op, net, addr string, deadline time.Time) (Addr, error) { return resolveInternetAddr(afnet, addr, deadline) } -// Dial connects to the address addr on the network net. +// Dial connects to the address on the named network. // // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), // "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" @@ -181,32 +117,33 @@ func resolveAddr(op, net, addr string, deadline time.Time) (Addr, error) { // Dial("tcp", "[2001:db8::1]:http") // Dial("tcp", "[fe80::1%lo0]:80") // -// For IP networks, the net must be "ip", "ip4" or "ip6" followed by a -// colon and a protocol number or name and the addr must be a literal -// IP address. +// For IP networks, the network must be "ip", "ip4" or "ip6" followed +// by a colon and a protocol number or name and the addr must be a +// literal IP address. // // Examples: // Dial("ip4:1", "127.0.0.1") // Dial("ip6:ospf", "::1") // -// For Unix networks, the addr must be a file system path. -func Dial(net, addr string) (Conn, error) { - return DialOpt(addr, dialNetwork(net)) +// For Unix networks, the address must be a file system path. +func Dial(network, address string) (Conn, error) { + var d Dialer + return d.Dial(network, address) } -// DialOpt dials addr using the provided options. -// If no options are provided, DialOpt(addr) is equivalent -// to Dial("tcp", addr). See Dial for the syntax of addr. -func DialOpt(addr string, opts ...DialOption) (Conn, error) { - var o dialOpts - for _, opt := range opts { - opt.setDialOpt(&o) - } - ra, err := resolveAddr("dial", o.net(), addr, o.deadline) - if err != nil { - return nil, err - } - return dial(o.net(), addr, o.localAddr, ra, o.deadline) +// DialTimeout acts like Dial but takes a timeout. +// The timeout includes name resolution, if required. +func DialTimeout(network, address string, timeout time.Duration) (Conn, error) { + d := Dialer{Timeout: timeout} + return d.Dial(network, address) +} + +// Dial connects to the address on the named network. +// +// See func Dial for a description of the network and address +// parameters. +func (d *Dialer) Dial(network, address string) (Conn, error) { + return resolveAndDial(network, address, d.LocalAddr, d.deadline()) } func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) { @@ -235,58 +172,6 @@ func dial(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) return } -// DialTimeout acts like Dial but takes a timeout. -// The timeout includes name resolution, if required. -func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) { - return dialTimeout(net, addr, timeout) -} - -// dialTimeoutRace is the old implementation of DialTimeout, still used -// on operating systems where the deadline hasn't been pushed down -// into the pollserver. -// TODO: fix this on plan9. -func dialTimeoutRace(net, addr string, timeout time.Duration) (Conn, error) { - t := time.NewTimer(timeout) - defer t.Stop() - type pair struct { - Conn - error - } - ch := make(chan pair, 1) - resolvedAddr := make(chan Addr, 1) - go func() { - ra, err := resolveAddr("dial", net, addr, noDeadline) - if err != nil { - ch <- pair{nil, err} - return - } - resolvedAddr <- ra // in case we need it for OpError - c, err := dial(net, addr, noLocalAddr, ra, noDeadline) - ch <- pair{c, err} - }() - select { - case <-t.C: - // Try to use the real Addr in our OpError, if we resolved it - // before the timeout. Otherwise we just use stringAddr. - var ra Addr - select { - case a := <-resolvedAddr: - ra = a - default: - ra = &stringAddr{net, addr} - } - err := &OpError{ - Op: "dial", - Net: net, - Addr: ra, - Err: &timeoutError{}, - } - return nil, err - case p := <-ch: - return p.Conn, p.error - } -} - type stringAddr struct { net, addr string } diff --git a/src/pkg/net/dial_gen.go b/src/pkg/net/dial_gen.go new file mode 100644 index 0000000000..0a3277de46 --- /dev/null +++ b/src/pkg/net/dial_gen.go @@ -0,0 +1,61 @@ +// Copyright 2012 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 windows plan9 + +package net + +import ( + "time" +) + +// resolveAndDialChannel is the simple pure-Go implementation of +// resolveAndDial, still used on operating systems where the deadline +// hasn't been pushed down into the pollserver. (Plan 9 and some old +// versions of Windows) +func resolveAndDialChannel(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) { + timeout := deadline.Sub(time.Now()) + if timeout < 0 { + timeout = 0 + } + t := time.NewTimer(timeout) + defer t.Stop() + type pair struct { + Conn + error + } + ch := make(chan pair, 1) + resolvedAddr := make(chan Addr, 1) + go func() { + ra, err := resolveAddr("dial", net, addr, noDeadline) + if err != nil { + ch <- pair{nil, err} + return + } + resolvedAddr <- ra // in case we need it for OpError + c, err := dial(net, addr, localAddr, ra, noDeadline) + ch <- pair{c, err} + }() + select { + case <-t.C: + // Try to use the real Addr in our OpError, if we resolved it + // before the timeout. Otherwise we just use stringAddr. + var ra Addr + select { + case a := <-resolvedAddr: + ra = a + default: + ra = &stringAddr{net, addr} + } + err := &OpError{ + Op: "dial", + Net: net, + Addr: ra, + Err: &timeoutError{}, + } + return nil, err + case p := <-ch: + return p.Conn, p.error + } +} diff --git a/src/pkg/net/fd_plan9.go b/src/pkg/net/fd_plan9.go index 169087999d..e9527a3743 100644 --- a/src/pkg/net/fd_plan9.go +++ b/src/pkg/net/fd_plan9.go @@ -23,10 +23,10 @@ var canCancelIO = true // used for testing current package func sysInit() { } -func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) { +func resolveAndDial(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) { // On plan9, use the relatively inefficient // goroutine-racing implementation. - return dialTimeoutRace(net, addr, timeout) + return resolveAndDialChannel(net, addr, localAddr, deadline) } func newFD(proto, name string, ctl, data *os.File, laddr, raddr Addr) *netFD { diff --git a/src/pkg/net/fd_unix.go b/src/pkg/net/fd_unix.go index 2b418a8681..f52c98a6c6 100644 --- a/src/pkg/net/fd_unix.go +++ b/src/pkg/net/fd_unix.go @@ -41,13 +41,12 @@ type netFD struct { pd pollDesc } -func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) { - deadline := time.Now().Add(timeout) +func resolveAndDial(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) { ra, err := resolveAddr("dial", net, addr, deadline) if err != nil { return nil, err } - return dial(net, addr, noLocalAddr, ra, deadline) + return dial(net, addr, localAddr, ra, deadline) } func newFD(fd, family, sotype int, net string) (*netFD, error) { diff --git a/src/pkg/net/fd_windows.go b/src/pkg/net/fd_windows.go index cacee627b6..3a16d8ae7b 100644 --- a/src/pkg/net/fd_windows.go +++ b/src/pkg/net/fd_windows.go @@ -54,18 +54,17 @@ func canUseConnectEx(net string) bool { return syscall.LoadConnectEx() == nil } -func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) { +func resolveAndDial(net, addr string, localAddr Addr, deadline time.Time) (Conn, error) { if !canUseConnectEx(net) { // Use the relatively inefficient goroutine-racing // implementation of DialTimeout. - return dialTimeoutRace(net, addr, timeout) + return resolveAndDialChannel(net, addr, localAddr, deadline) } - deadline := time.Now().Add(timeout) ra, err := resolveAddr("dial", net, addr, deadline) if err != nil { return nil, err } - return dial(net, addr, noLocalAddr, ra, deadline) + return dial(net, addr, localAddr, ra, deadline) } // Interface for all IO operations. -- 2.50.0