diff options
Diffstat (limited to 'libgo/go/net')
30 files changed, 424 insertions, 180 deletions
diff --git a/libgo/go/net/cgo_unix_test.go b/libgo/go/net/cgo_unix_test.go new file mode 100644 index 00000000000..33566ce9c2e --- /dev/null +++ b/libgo/go/net/cgo_unix_test.go @@ -0,0 +1,24 @@ +// Copyright 2013 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 cgo,!netgo +// +build darwin dragonfly freebsd linux netbsd openbsd + +package net + +import "testing" + +func TestCgoLookupIP(t *testing.T) { + host := "localhost" + _, err, ok := cgoLookupIP(host) + if !ok { + t.Errorf("cgoLookupIP must not be a placeholder") + } + if err != nil { + t.Errorf("cgoLookupIP failed: %v", err) + } + if _, err := goLookupIP(host); err != nil { + t.Errorf("goLookupIP failed: %v", err) + } +} diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index 6304818bf14..70b66e70d15 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -172,7 +172,6 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Conn, error) { type racer struct { Conn - Addr error } // Sig controls the flow of dial results on lane. It passes a @@ -184,7 +183,7 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con go func(ra Addr) { c, err := dialSingle(net, addr, la, ra, deadline) if _, ok := <-sig; ok { - lane <- racer{c, ra, err} + lane <- racer{c, err} } else if err == nil { // We have to return the resources // that belong to the other @@ -195,7 +194,6 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con }(ra.toAddr()) } defer close(sig) - var failAddr Addr lastErr := errTimeout nracers := len(ras) for nracers > 0 { @@ -205,12 +203,11 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con if racer.error == nil { return racer.Conn, nil } - failAddr = racer.Addr lastErr = racer.error nracers-- } } - return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr} + return nil, lastErr } // dialSingle attempts to establish and returns a single connection to diff --git a/libgo/go/net/dialgoogle_test.go b/libgo/go/net/dialgoogle_test.go index b4ebad0e0dc..79d150f8aad 100644 --- a/libgo/go/net/dialgoogle_test.go +++ b/libgo/go/net/dialgoogle_test.go @@ -107,30 +107,6 @@ var googleaddrsipv4 = []string{ "[0:0:0:0:0:ffff::%d.%d.%d.%d]:80", } -func TestDNSThreadLimit(t *testing.T) { - if testing.Short() || !*testExternal { - t.Skip("skipping test to avoid external network") - } - - const N = 10000 - c := make(chan int, N) - for i := 0; i < N; i++ { - go func(i int) { - LookupIP(fmt.Sprintf("%d.net-test.golang.org", i)) - c <- 1 - }(i) - } - // Don't bother waiting for the stragglers; stop at 0.9 N. - for i := 0; i < N*9/10; i++ { - if i%100 == 0 { - //println("TestDNSThreadLimit:", i) - } - <-c - } - - // If we're still here, it worked. -} - func TestDialGoogleIPv4(t *testing.T) { if testing.Short() || !*testExternal { t.Skip("skipping test to avoid external network") diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go index 16cf420dcdb..a30c9a73d7e 100644 --- a/libgo/go/net/dnsclient_unix.go +++ b/libgo/go/net/dnsclient_unix.go @@ -159,7 +159,8 @@ func convertRR_AAAA(records []dnsRR) []IP { var cfg *dnsConfig var dnserr error -func loadConfig() { cfg, dnserr = dnsReadConfig() } +// Assume dns config file is /etc/resolv.conf here +func loadConfig() { cfg, dnserr = dnsReadConfig("/etc/resolv.conf") } var onceLoadConfig sync.Once diff --git a/libgo/go/net/dnsconfig_unix.go b/libgo/go/net/dnsconfig_unix.go index 2f0f6c031f1..7856ebc80de 100644 --- a/libgo/go/net/dnsconfig_unix.go +++ b/libgo/go/net/dnsconfig_unix.go @@ -20,14 +20,13 @@ type dnsConfig struct { // See resolv.conf(5) on a Linux machine. // TODO(rsc): Supposed to call uname() and chop the beginning // of the host name to get the default search domain. -// We assume it's in resolv.conf anyway. -func dnsReadConfig() (*dnsConfig, error) { - file, err := open("/etc/resolv.conf") +func dnsReadConfig(filename string) (*dnsConfig, error) { + file, err := open(filename) if err != nil { return nil, &DNSConfigError{err} } conf := new(dnsConfig) - conf.servers = make([]string, 3)[0:0] // small, but the standard limit + conf.servers = make([]string, 0, 3) // small, but the standard limit conf.search = make([]string, 0) conf.ndots = 1 conf.timeout = 5 diff --git a/libgo/go/net/dnsconfig_unix_test.go b/libgo/go/net/dnsconfig_unix_test.go new file mode 100644 index 00000000000..697c69f9959 --- /dev/null +++ b/libgo/go/net/dnsconfig_unix_test.go @@ -0,0 +1,46 @@ +// Copyright 2013 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 darwin dragonfly freebsd linux netbsd openbsd + +package net + +import "testing" + +func TestDNSReadConfig(t *testing.T) { + dnsConfig, err := dnsReadConfig("testdata/resolv.conf") + if err != nil { + t.Fatal(err) + } + + if len(dnsConfig.servers) != 1 { + t.Errorf("len(dnsConfig.servers) = %d; want %d", len(dnsConfig.servers), 1) + } + if dnsConfig.servers[0] != "[192.168.1.1]" { + t.Errorf("dnsConfig.servers[0] = %s; want %s", dnsConfig.servers[0], "[192.168.1.1]") + } + + if len(dnsConfig.search) != 1 { + t.Errorf("len(dnsConfig.search) = %d; want %d", len(dnsConfig.search), 1) + } + if dnsConfig.search[0] != "Home" { + t.Errorf("dnsConfig.search[0] = %s; want %s", dnsConfig.search[0], "Home") + } + + if dnsConfig.ndots != 5 { + t.Errorf("dnsConfig.ndots = %d; want %d", dnsConfig.ndots, 5) + } + + if dnsConfig.timeout != 10 { + t.Errorf("dnsConfig.timeout = %d; want %d", dnsConfig.timeout, 10) + } + + if dnsConfig.attempts != 3 { + t.Errorf("dnsConfig.attempts = %d; want %d", dnsConfig.attempts, 3) + } + + if dnsConfig.rotate != true { + t.Errorf("dnsConfig.rotate = %t; want %t", dnsConfig.rotate, true) + } +} diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go index 630fc5e6f71..64d56c73e06 100644 --- a/libgo/go/net/fd_windows.go +++ b/libgo/go/net/fd_windows.go @@ -513,7 +513,12 @@ func (fd *netFD) WriteTo(buf []byte, sa syscall.Sockaddr) (int, error) { }) } -func (fd *netFD) acceptOne(toAddr func(syscall.Sockaddr) Addr, rawsa []syscall.RawSockaddrAny, o *operation) (*netFD, error) { +func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { + if err := fd.readLock(); err != nil { + return nil, err + } + defer fd.readUnlock() + // Get new socket. s, err := sysSocket(fd.family, fd.sotype, 0) if err != nil { @@ -532,7 +537,9 @@ func (fd *netFD) acceptOne(toAddr func(syscall.Sockaddr) Addr, rawsa []syscall.R } // Submit accept request. + o := &fd.rop o.handle = s + var rawsa [2]syscall.RawSockaddrAny o.rsan = int32(unsafe.Sizeof(rawsa[0])) _, err = rsrv.ExecIO(o, "AcceptEx", func(o *operation) error { return syscall.AcceptEx(o.fd.sysfd, o.handle, (*byte)(unsafe.Pointer(&rawsa[0])), 0, uint32(o.rsan), uint32(o.rsan), &o.qty, &o.o) @@ -549,45 +556,6 @@ func (fd *netFD) acceptOne(toAddr func(syscall.Sockaddr) Addr, rawsa []syscall.R return nil, &OpError{"Setsockopt", fd.net, fd.laddr, err} } - return netfd, nil -} - -func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { - if err := fd.readLock(); err != nil { - return nil, err - } - defer fd.readUnlock() - - o := &fd.rop - var netfd *netFD - var err error - var rawsa [2]syscall.RawSockaddrAny - for { - netfd, err = fd.acceptOne(toAddr, rawsa[:], o) - if err == nil { - break - } - // Sometimes we see WSAECONNRESET and ERROR_NETNAME_DELETED is - // returned here. These happen if connection reset is received - // before AcceptEx could complete. These errors relate to new - // connection, not to AcceptEx, so ignore broken connection and - // try AcceptEx again for more connections. - operr, ok := err.(*OpError) - if !ok { - return nil, err - } - errno, ok := operr.Err.(syscall.Errno) - if !ok { - return nil, err - } - switch errno { - case syscall.ERROR_NETNAME_DELETED, syscall.WSAECONNRESET: - // ignore these and try again - default: - return nil, err - } - } - // Get local and peer addr out of AcceptEx buffer. var lrsa, rrsa *syscall.RawSockaddrAny var llen, rlen int32 diff --git a/libgo/go/net/hosts_test.go b/libgo/go/net/hosts_test.go index b07ed0baa94..2fe358e079c 100644 --- a/libgo/go/net/hosts_test.go +++ b/libgo/go/net/hosts_test.go @@ -41,7 +41,7 @@ func TestLookupStaticHost(t *testing.T) { if len(ips) != len(tt.ips) { t.Errorf("# of hosts = %v; want %v", len(ips), len(tt.ips)) - return + continue } for k, v := range ips { if tt.ips[k].String() != v { diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index 997d04151c2..e5ad39c7741 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -373,24 +373,6 @@ func (j *TestJar) Cookies(u *url.URL) []*Cookie { return j.perURL[u.Host] } -func TestRedirectCookiesOnRequest(t *testing.T) { - defer afterTest(t) - var ts *httptest.Server - ts = httptest.NewServer(echoCookiesRedirectHandler) - defer ts.Close() - c := &Client{} - req, _ := NewRequest("GET", ts.URL, nil) - req.AddCookie(expectedCookies[0]) - // TODO: Uncomment when an implementation of a RFC6265 cookie jar lands. - _ = c - // resp, _ := c.Do(req) - // matchReturnedCookies(t, expectedCookies, resp.Cookies()) - - req, _ = NewRequest("GET", ts.URL, nil) - // resp, _ = c.Do(req) - // matchReturnedCookies(t, expectedCookies[1:], resp.Cookies()) -} - func TestRedirectCookiesJar(t *testing.T) { defer afterTest(t) var ts *httptest.Server @@ -410,8 +392,8 @@ func TestRedirectCookiesJar(t *testing.T) { } func matchReturnedCookies(t *testing.T, expected, given []*Cookie) { - t.Logf("Received cookies: %v", given) if len(given) != len(expected) { + t.Logf("Received cookies: %v", given) t.Errorf("Expected %d cookies, got %d", len(expected), len(given)) } for _, ec := range expected { diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index 8b01c508eb1..a1759214f38 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -94,7 +94,6 @@ func readSetCookies(h Header) []*Cookie { continue case "domain": c.Domain = val - // TODO: Add domain parsing continue case "max-age": secs, err := strconv.Atoi(val) @@ -121,7 +120,6 @@ func readSetCookies(h Header) []*Cookie { continue case "path": c.Path = val - // TODO: Add path parsing continue } c.Unparsed = append(c.Unparsed, parts[i]) diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index 11b01cc5713..1aa9d49d96e 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -5,9 +5,13 @@ package http import ( + "bytes" "encoding/json" "fmt" + "log" + "os" "reflect" + "strings" "testing" "time" ) @@ -51,12 +55,20 @@ var writeSetCookiesTests = []struct { } func TestWriteSetCookies(t *testing.T) { + defer log.SetOutput(os.Stderr) + var logbuf bytes.Buffer + log.SetOutput(&logbuf) + for i, tt := range writeSetCookiesTests { if g, e := tt.Cookie.String(), tt.Raw; g != e { t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g) continue } } + + if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) { + t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) + } } type headerOnlyResponseWriter Header @@ -244,6 +256,10 @@ func TestReadCookies(t *testing.T) { } func TestCookieSanitizeValue(t *testing.T) { + defer log.SetOutput(os.Stderr) + var logbuf bytes.Buffer + log.SetOutput(&logbuf) + tests := []struct { in, want string }{ @@ -257,9 +273,17 @@ func TestCookieSanitizeValue(t *testing.T) { t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want) } } + + if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { + t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) + } } func TestCookieSanitizePath(t *testing.T) { + defer log.SetOutput(os.Stderr) + var logbuf bytes.Buffer + log.SetOutput(&logbuf) + tests := []struct { in, want string }{ @@ -272,4 +296,8 @@ func TestCookieSanitizePath(t *testing.T) { t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want) } } + + if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { + t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) + } } diff --git a/libgo/go/net/http/cookiejar/jar.go b/libgo/go/net/http/cookiejar/jar.go index 389ab58e418..82f18a17848 100644 --- a/libgo/go/net/http/cookiejar/jar.go +++ b/libgo/go/net/http/cookiejar/jar.go @@ -34,9 +34,9 @@ import ( type PublicSuffixList interface { // PublicSuffix returns the public suffix of domain. // - // TODO: specify which of the caller and callee is responsible for IP - // addresses, for leading and trailing dots, for case sensitivity, and - // for IDN/Punycode. + // Domain is a lowercase punycoded domain name (not an IP address) + // without leading or trailing dots. The returned value is in the + // same form. PublicSuffix(domain string) string // String returns a description of the source of this public suffix diff --git a/libgo/go/net/http/header.go b/libgo/go/net/http/header.go index ca1ae07c25d..de62bef5525 100644 --- a/libgo/go/net/http/header.go +++ b/libgo/go/net/http/header.go @@ -9,6 +9,7 @@ import ( "net/textproto" "sort" "strings" + "sync" "time" ) @@ -114,18 +115,15 @@ func (s *headerSorter) Len() int { return len(s.kvs) } func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] } func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key } -// TODO: convert this to a sync.Cache (issue 4720) -var headerSorterCache = make(chan *headerSorter, 8) +var headerSorterPool = sync.Pool{ + New: func() interface{} { return new(headerSorter) }, +} // sortedKeyValues returns h's keys sorted in the returned kvs // slice. The headerSorter used to sort is also returned, for possible // return to headerSorterCache. func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) { - select { - case hs = <-headerSorterCache: - default: - hs = new(headerSorter) - } + hs = headerSorterPool.Get().(*headerSorter) if cap(hs.kvs) < len(h) { hs.kvs = make([]keyValues, 0, len(h)) } @@ -159,10 +157,7 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { } } } - select { - case headerSorterCache <- sorter: - default: - } + headerSorterPool.Put(sorter) return nil } diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index 57b5d094847..7a97770314d 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -20,6 +20,7 @@ import ( "net/url" "strconv" "strings" + "sync" ) const ( @@ -494,25 +495,20 @@ func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { return line[:s1], line[s1+1 : s2], line[s2+1:], true } -// TODO(bradfitz): use a sync.Cache when available -var textprotoReaderCache = make(chan *textproto.Reader, 4) +var textprotoReaderPool sync.Pool func newTextprotoReader(br *bufio.Reader) *textproto.Reader { - select { - case r := <-textprotoReaderCache: - r.R = br - return r - default: - return textproto.NewReader(br) + if v := textprotoReaderPool.Get(); v != nil { + tr := v.(*textproto.Reader) + tr.R = br + return tr } + return textproto.NewReader(br) } func putTextprotoReader(r *textproto.Reader) { r.R = nil - select { - case textprotoReaderCache <- r: - default: - } + textprotoReaderPool.Put(r) } // ReadRequest reads and parses a request from b. @@ -677,6 +673,11 @@ func parsePostForm(r *Request) (vs url.Values, err error) { return } ct := r.Header.Get("Content-Type") + // RFC 2616, section 7.2.1 - empty type + // SHOULD be treated as application/octet-stream + if ct == "" { + ct = "application/octet-stream" + } ct, _, err = mime.ParseMediaType(ct) switch { case ct == "application/x-www-form-urlencoded": @@ -707,7 +708,7 @@ func parsePostForm(r *Request) (vs url.Values, err error) { // orders to call too many functions here. // Clean this up and write more tests. // request_test.go contains the start of this, - // in TestRequestMultipartCallOrder. + // in TestParseMultipartFormOrder and others. } return } diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index 89303c33602..0c1e16b8d5e 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -68,8 +68,9 @@ type parseContentTypeTest struct { var parseContentTypeTests = []parseContentTypeTest{ {false, stringMap{"Content-Type": {"text/plain"}}}, - // Non-existent keys are not placed. The value nil is illegal. - {true, stringMap{}}, + // Empty content type is legal - shoult be treated as + // application/octet-stream (RFC 2616, section 7.2.1) + {false, stringMap{}}, {true, stringMap{"Content-Type": {"text/plain; boundary="}}}, {false, stringMap{"Content-Type": {"application/unknown"}}}, } @@ -198,15 +199,39 @@ func TestEmptyMultipartRequest(t *testing.T) { testMissingFile(t, req) } -func TestRequestMultipartCallOrder(t *testing.T) { +// Test that ParseMultipartForm errors if called +// after MultipartReader on the same request. +func TestParseMultipartFormOrder(t *testing.T) { req := newTestMultipartRequest(t) - _, err := req.MultipartReader() - if err != nil { + if _, err := req.MultipartReader(); err != nil { + t.Fatalf("MultipartReader: %v", err) + } + if err := req.ParseMultipartForm(1024); err == nil { + t.Fatal("expected an error from ParseMultipartForm after call to MultipartReader") + } +} + +// Test that MultipartReader errors if called +// after ParseMultipartForm on the same request. +func TestMultipartReaderOrder(t *testing.T) { + req := newTestMultipartRequest(t) + if err := req.ParseMultipartForm(25); err != nil { + t.Fatalf("ParseMultipartForm: %v", err) + } + if _, err := req.MultipartReader(); err == nil { + t.Fatal("expected an error from MultipartReader after call to ParseMultipartForm") + } +} + +// Test that FormFile errors if called after +// MultipartReader on the same request. +func TestFormFileOrder(t *testing.T) { + req := newTestMultipartRequest(t) + if _, err := req.MultipartReader(); err != nil { t.Fatalf("MultipartReader: %v", err) } - err = req.ParseMultipartForm(1024) - if err == nil { - t.Errorf("expected an error from ParseMultipartForm after call to MultipartReader") + if _, _, err := req.FormFile(""); err == nil { + t.Fatal("expected an error from FormFile after call to MultipartReader") } } diff --git a/libgo/go/net/http/response.go b/libgo/go/net/http/response.go index 35d0ba3bb15..2ec1d408728 100644 --- a/libgo/go/net/http/response.go +++ b/libgo/go/net/http/response.go @@ -187,6 +187,7 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { // ContentLength // Header, values for non-canonical keys will have unpredictable behavior // +// Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { // Status line diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go index 5044306a876..f73172189e9 100644 --- a/libgo/go/net/http/response_test.go +++ b/libgo/go/net/http/response_test.go @@ -14,6 +14,7 @@ import ( "io/ioutil" "net/url" "reflect" + "regexp" "strings" "testing" ) @@ -406,8 +407,7 @@ func TestWriteResponse(t *testing.T) { t.Errorf("#%d: %v", i, err) continue } - bout := bytes.NewBuffer(nil) - err = resp.Write(bout) + err = resp.Write(ioutil.Discard) if err != nil { t.Errorf("#%d: %v", i, err) continue @@ -506,6 +506,9 @@ func TestReadResponseCloseInMiddle(t *testing.T) { rest, err := ioutil.ReadAll(bufr) checkErr(err, "ReadAll on remainder") if e, g := "Next Request Here", string(rest); e != g { + g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { + return fmt.Sprintf("x(repeated x%d)", len(match)) + }) fatalf("remainder = %q, expected %q", g, e) } } diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index 8961cf491f8..1dba1873ea5 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -1934,6 +1934,31 @@ func TestWriteAfterHijack(t *testing.T) { } } +func TestDoubleHijack(t *testing.T) { + req := reqBytes("GET / HTTP/1.1\nHost: golang.org") + var buf bytes.Buffer + conn := &rwTestConn{ + Reader: bytes.NewReader(req), + Writer: &buf, + closec: make(chan bool, 1), + } + handler := HandlerFunc(func(rw ResponseWriter, r *Request) { + conn, _, err := rw.(Hijacker).Hijack() + if err != nil { + t.Error(err) + return + } + _, _, err = rw.(Hijacker).Hijack() + if err == nil { + t.Errorf("got err = nil; want err != nil") + } + conn.Close() + }) + ln := &oneConnListener{conn: conn} + go Serve(ln, handler) + <-conn.closec +} + // http://code.google.com/p/go/issues/detail?id=5955 // Note that this does not test the "request too large" // exit path from the http server. This is intentional; @@ -2065,6 +2090,64 @@ func TestNoContentTypeOnNotModified(t *testing.T) { } } +// Issue 6995 +// A server Handler can receive a Request, and then turn around and +// give a copy of that Request.Body out to the Transport (e.g. any +// proxy). So then two people own that Request.Body (both the server +// and the http client), and both think they can close it on failure. +// Therefore, all incoming server requests Bodies need to be thread-safe. +func TestTransportAndServerSharedBodyRace(t *testing.T) { + defer afterTest(t) + + const bodySize = 1 << 20 + + unblockBackend := make(chan bool) + backend := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + io.CopyN(rw, req.Body, bodySize/2) + <-unblockBackend + })) + defer backend.Close() + + backendRespc := make(chan *Response, 1) + proxy := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + if req.RequestURI == "/foo" { + rw.Write([]byte("bar")) + return + } + req2, _ := NewRequest("POST", backend.URL, req.Body) + req2.ContentLength = bodySize + + bresp, err := DefaultClient.Do(req2) + if err != nil { + t.Errorf("Proxy outbound request: %v", err) + return + } + _, err = io.CopyN(ioutil.Discard, bresp.Body, bodySize/4) + if err != nil { + t.Errorf("Proxy copy error: %v", err) + return + } + backendRespc <- bresp // to close later + + // Try to cause a race: Both the DefaultTransport and the proxy handler's Server + // will try to read/close req.Body (aka req2.Body) + DefaultTransport.(*Transport).CancelRequest(req2) + rw.Write([]byte("OK")) + })) + defer proxy.Close() + + req, _ := NewRequest("POST", proxy.URL, io.LimitReader(neverEnding('a'), bodySize)) + res, err := DefaultClient.Do(req) + if err != nil { + t.Fatalf("Original request: %v", err) + } + + // Cleanup, so we don't leak goroutines. + res.Body.Close() + close(unblockBackend) + (<-backendRespc).Body.Close() +} + func TestResponseWriterWriteStringAllocs(t *testing.T) { t.Skip("allocs test unreliable with gccgo") ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -2391,3 +2474,28 @@ Host: golang.org b.Errorf("b.N=%d but handled %d", b.N, handled) } } + +func BenchmarkServerHijack(b *testing.B) { + b.ReportAllocs() + req := reqBytes(`GET / HTTP/1.1 +Host: golang.org +`) + h := HandlerFunc(func(w ResponseWriter, r *Request) { + conn, _, err := w.(Hijacker).Hijack() + if err != nil { + panic(err) + } + conn.Close() + }) + conn := &rwTestConn{ + Writer: ioutil.Discard, + closec: make(chan bool, 1), + } + ln := &oneConnListener{conn: conn} + for i := 0; i < b.N; i++ { + conn.Reader = bytes.NewReader(req) + ln.conn = conn + Serve(ln, h) + <-conn.closec + } +} diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index 0e46863d5ae..7ebd8575f3b 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -435,56 +435,52 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) { return c, nil } -// TODO: use a sync.Cache instead var ( - bufioReaderCache = make(chan *bufio.Reader, 4) - bufioWriterCache2k = make(chan *bufio.Writer, 4) - bufioWriterCache4k = make(chan *bufio.Writer, 4) + bufioReaderPool sync.Pool + bufioWriter2kPool sync.Pool + bufioWriter4kPool sync.Pool ) -func bufioWriterCache(size int) chan *bufio.Writer { +func bufioWriterPool(size int) *sync.Pool { switch size { case 2 << 10: - return bufioWriterCache2k + return &bufioWriter2kPool case 4 << 10: - return bufioWriterCache4k + return &bufioWriter4kPool } return nil } func newBufioReader(r io.Reader) *bufio.Reader { - select { - case p := <-bufioReaderCache: - p.Reset(r) - return p - default: - return bufio.NewReader(r) + if v := bufioReaderPool.Get(); v != nil { + br := v.(*bufio.Reader) + br.Reset(r) + return br } + return bufio.NewReader(r) } func putBufioReader(br *bufio.Reader) { br.Reset(nil) - select { - case bufioReaderCache <- br: - default: - } + bufioReaderPool.Put(br) } func newBufioWriterSize(w io.Writer, size int) *bufio.Writer { - select { - case p := <-bufioWriterCache(size): - p.Reset(w) - return p - default: - return bufio.NewWriterSize(w, size) + pool := bufioWriterPool(size) + if pool != nil { + if v := pool.Get(); v != nil { + bw := v.(*bufio.Writer) + bw.Reset(w) + return bw + } } + return bufio.NewWriterSize(w, size) } func putBufioWriter(bw *bufio.Writer) { bw.Reset(nil) - select { - case bufioWriterCache(bw.Available()) <- bw: - default: + if pool := bufioWriterPool(bw.Available()); pool != nil { + pool.Put(bw) } } @@ -1202,7 +1198,14 @@ func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) { if w.wroteHeader { w.cw.flush() } - return w.conn.hijack() + // Release the bufioWriter that writes to the chunk writer, it is not + // used after a connection has been hijacked. + rwc, buf, err = w.conn.hijack() + if err == nil { + putBufioWriter(w.w) + w.w = nil + } + return rwc, buf, err } func (w *response) CloseNotify() <-chan bool { diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index bacd83732de..4a2bda19fac 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -14,6 +14,7 @@ import ( "net/textproto" "strconv" "strings" + "sync" ) // transferWriter inspects the fields of a user-supplied Request or Response, @@ -331,17 +332,17 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { if noBodyExpected(t.RequestMethod) { t.Body = eofReader } else { - t.Body = &body{Reader: newChunkedReader(r), hdr: msg, r: r, closing: t.Close} + t.Body = &body{src: newChunkedReader(r), hdr: msg, r: r, closing: t.Close} } case realLength == 0: t.Body = eofReader case realLength > 0: - t.Body = &body{Reader: io.LimitReader(r, realLength), closing: t.Close} + t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close} default: // realLength < 0, i.e. "Content-Length" not mentioned in header if t.Close { // Close semantics (i.e. HTTP/1.0) - t.Body = &body{Reader: r, closing: t.Close} + t.Body = &body{src: r, closing: t.Close} } else { // Persistent connection (i.e. HTTP/1.1) t.Body = eofReader @@ -514,11 +515,13 @@ func fixTrailer(header Header, te []string) (Header, error) { // Close ensures that the body has been fully read // and then reads the trailer if necessary. type body struct { - io.Reader + src io.Reader hdr interface{} // non-nil (Response or Request) value means read trailer r *bufio.Reader // underlying wire-format reader for the trailer closing bool // is the connection to be closed after reading body? - closed bool + + mu sync.Mutex // guards closed, and calls to Read and Close + closed bool } // ErrBodyReadAfterClose is returned when reading a Request or Response @@ -528,10 +531,17 @@ type body struct { var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body") func (b *body) Read(p []byte) (n int, err error) { + b.mu.Lock() + defer b.mu.Unlock() if b.closed { return 0, ErrBodyReadAfterClose } - n, err = b.Reader.Read(p) + return b.readLocked(p) +} + +// Must hold b.mu. +func (b *body) readLocked(p []byte) (n int, err error) { + n, err = b.src.Read(p) if err == io.EOF { // Chunked case. Read the trailer. @@ -543,7 +553,7 @@ func (b *body) Read(p []byte) (n int, err error) { } else { // If the server declared the Content-Length, our body is a LimitedReader // and we need to check whether this EOF arrived early. - if lr, ok := b.Reader.(*io.LimitedReader); ok && lr.N > 0 { + if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > 0 { err = io.ErrUnexpectedEOF } } @@ -618,6 +628,8 @@ func (b *body) readTrailer() error { } func (b *body) Close() error { + b.mu.Lock() + defer b.mu.Unlock() if b.closed { return nil } @@ -629,12 +641,25 @@ func (b *body) Close() error { default: // Fully consume the body, which will also lead to us reading // the trailer headers after the body, if present. - _, err = io.Copy(ioutil.Discard, b) + _, err = io.Copy(ioutil.Discard, bodyLocked{b}) } b.closed = true return err } +// bodyLocked is a io.Reader reading from a *body when its mutex is +// already held. +type bodyLocked struct { + b *body +} + +func (bl bodyLocked) Read(p []byte) (n int, err error) { + if bl.b.closed { + return 0, ErrBodyReadAfterClose + } + return bl.b.readLocked(p) +} + // parseContentLength trims whitespace from s and returns -1 if no value // is set, or the value if it's >= 0. func parseContentLength(cl string) (int64, error) { diff --git a/libgo/go/net/http/transfer_test.go b/libgo/go/net/http/transfer_test.go index 8627a374c8f..fb5ef37a0f0 100644 --- a/libgo/go/net/http/transfer_test.go +++ b/libgo/go/net/http/transfer_test.go @@ -12,9 +12,9 @@ import ( func TestBodyReadBadTrailer(t *testing.T) { b := &body{ - Reader: strings.NewReader("foobar"), - hdr: true, // force reading the trailer - r: bufio.NewReader(strings.NewReader("")), + src: strings.NewReader("foobar"), + hdr: true, // force reading the trailer + r: bufio.NewReader(strings.NewReader("")), } buf := make([]byte, 7) n, err := b.Read(buf[:3]) diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index e4df30a98de..2ce2b6b5180 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -798,8 +798,8 @@ func TestTransportPersistConnLeak(t *testing.T) { // We expect 0 or 1 extra goroutine, empirically. Allow up to 5. // Previously we were leaking one per numReq. - t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth) if int(growth) > 5 { + t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth) t.Error("too many new goroutines") } } diff --git a/libgo/go/net/ip.go b/libgo/go/net/ip.go index fd6a7d4ee8b..0582009b8bd 100644 --- a/libgo/go/net/ip.go +++ b/libgo/go/net/ip.go @@ -623,6 +623,9 @@ func parseIPv6(s string, zoneAllowed bool) (ip IP, zone string) { for k := ellipsis + n - 1; k >= ellipsis; k-- { ip[k] = 0 } + } else if ellipsis >= 0 { + // Ellipsis must represent at least one 0 group. + return nil, zone } return ip, zone } diff --git a/libgo/go/net/ip_test.go b/libgo/go/net/ip_test.go index 26b53729b85..ffeb9d315e7 100644 --- a/libgo/go/net/ip_test.go +++ b/libgo/go/net/ip_test.go @@ -25,6 +25,7 @@ var parseIPTests = []struct { {"fe80::1%lo0", nil}, {"fe80::1%911", nil}, {"", nil}, + {"a1:a2:a3:a4::b1:b2:b3:b4", nil}, // Issue 6628 } func TestParseIP(t *testing.T) { diff --git a/libgo/go/net/iprawsock_posix.go b/libgo/go/net/iprawsock_posix.go index 72285325761..a1a008ac413 100644 --- a/libgo/go/net/iprawsock_posix.go +++ b/libgo/go/net/iprawsock_posix.go @@ -19,7 +19,7 @@ import ( // that you do not uses these methods if it is important to receive a // full packet. // -// The Go 1 compatibliity guidelines make it impossible for us to +// The Go 1 compatibility guidelines make it impossible for us to // change the behavior of these methods; use Read or ReadMsgIP // instead. diff --git a/libgo/go/net/lookup_plan9.go b/libgo/go/net/lookup_plan9.go index f1204a99f7b..a755ff2aacc 100644 --- a/libgo/go/net/lookup_plan9.go +++ b/libgo/go/net/lookup_plan9.go @@ -69,10 +69,31 @@ func queryDNS(addr string, typ string) (res []string, err error) { return query("/net/dns", addr+" "+typ, 1024) } +// toLower returns a lower-case version of in. Restricting us to +// ASCII is sufficient to handle the IP protocol names and allow +// us to not depend on the strings and unicode packages. +func toLower(in string) string { + for _, c := range in { + if 'A' <= c && c <= 'Z' { + // Has upper case; need to fix. + out := []byte(in) + for i := 0; i < len(in); i++ { + c := in[i] + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + out[i] = c + } + return string(out) + } + } + return in +} + // lookupProtocol looks up IP protocol name and returns // the corresponding protocol number. func lookupProtocol(name string) (proto int, err error) { - lines, err := query("/net/cs", "!protocol="+name, 128) + lines, err := query("/net/cs", "!protocol="+toLower(name), 128) if err != nil { return 0, err } @@ -94,7 +115,7 @@ func lookupProtocol(name string) (proto int, err error) { func lookupHost(host string) (addrs []string, err error) { // Use /net/cs instead of /net/dns because cs knows about // host names in local network (e.g. from /lib/ndb/local) - lines, err := queryCS("tcp", host, "1") + lines, err := queryCS("net", host, "1") if err != nil { return } diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go index 1320096df8f..c9fb433ec91 100644 --- a/libgo/go/net/net_test.go +++ b/libgo/go/net/net_test.go @@ -231,12 +231,12 @@ func TestErrorNil(t *testing.T) { // Make Listen fail by relistening on the same address. l, err := Listen("tcp", "127.0.0.1:0") if err != nil { - t.Fatal("Listen 127.0.0.1:0: %v", err) + t.Fatalf("Listen 127.0.0.1:0: %v", err) } defer l.Close() l1, err := Listen("tcp", l.Addr().String()) if err == nil { - t.Fatal("second Listen %v: %v", l.Addr(), err) + t.Fatalf("second Listen %v: %v", l.Addr(), err) } if l1 != nil { t.Fatalf("Listen returned non-nil interface %T(%v) with err != nil", l1, l1) @@ -245,12 +245,12 @@ func TestErrorNil(t *testing.T) { // Make ListenPacket fail by relistening on the same address. lp, err := ListenPacket("udp", "127.0.0.1:0") if err != nil { - t.Fatal("Listen 127.0.0.1:0: %v", err) + t.Fatalf("Listen 127.0.0.1:0: %v", err) } defer lp.Close() lp1, err := ListenPacket("udp", lp.LocalAddr().String()) if err == nil { - t.Fatal("second Listen %v: %v", lp.LocalAddr(), err) + t.Fatalf("second Listen %v: %v", lp.LocalAddr(), err) } if lp1 != nil { t.Fatalf("ListenPacket returned non-nil interface %T(%v) with err != nil", lp1, lp1) diff --git a/libgo/go/net/parse.go b/libgo/go/net/parse.go index 6056de248e0..ee6e7e99522 100644 --- a/libgo/go/net/parse.go +++ b/libgo/go/net/parse.go @@ -67,7 +67,7 @@ func open(name string) (*file, error) { if err != nil { return nil, err } - return &file{fd, make([]byte, os.Getpagesize())[0:0], false}, nil + return &file{fd, make([]byte, 0, os.Getpagesize()), false}, nil } func byteIndex(s string, c byte) int { diff --git a/libgo/go/net/testdata/resolv.conf b/libgo/go/net/testdata/resolv.conf new file mode 100644 index 00000000000..b5972e09c98 --- /dev/null +++ b/libgo/go/net/testdata/resolv.conf @@ -0,0 +1,5 @@ +# /etc/resolv.conf + +domain Home +nameserver 192.168.1.1 +options ndots:5 timeout:10 attempts:3 rotate diff --git a/libgo/go/net/z_last_test.go b/libgo/go/net/z_last_test.go new file mode 100644 index 00000000000..bb00f110fe9 --- /dev/null +++ b/libgo/go/net/z_last_test.go @@ -0,0 +1,34 @@ +// Copyright 2009 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. + +package net + +import ( + "fmt" + "testing" +) + +func TestDNSThreadLimit(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("skipping test to avoid external network") + } + + const N = 10000 + c := make(chan int, N) + for i := 0; i < N; i++ { + go func(i int) { + LookupIP(fmt.Sprintf("%d.net-test.golang.org", i)) + c <- 1 + }(i) + } + // Don't bother waiting for the stragglers; stop at 0.9 N. + for i := 0; i < N*9/10; i++ { + if i%100 == 0 { + //println("TestDNSThreadLimit:", i) + } + <-c + } + + // If we're still here, it worked. +} |