})
func TestClientSendsCookieFromJar(t *testing.T) {
+ defer afterTest(t)
tr := &recordingTransport{}
client := &Client{Transport: tr}
client.Jar = &TestJar{perURL: make(map[string][]*Cookie)}
var ExportServerNewConn = (*Server).newConn
var ExportCloseWriteAndWait = (*conn).closeWriteAndWait
+
+var ExportErrRequestCanceled = errRequestCanceled
// not counting goroutines for leakage in -short mode
return false
}
- gs := interestingGoroutines()
- n := 0
- stackCount := make(map[string]int)
- for _, g := range gs {
- stackCount[g]++
- n++
- }
-
- if n == 0 {
- return false
+ var stackCount map[string]int
+ for i := 0; i < 5; i++ {
+ n := 0
+ stackCount = make(map[string]int)
+ gs := interestingGoroutines()
+ for _, g := range gs {
+ stackCount[g]++
+ n++
+ }
+ if n == 0 {
+ return false
+ }
+ // Wait for goroutines to schedule and die off:
+ time.Sleep(100 * time.Millisecond)
}
fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n")
for stack, count := range stackCount {
import (
"bufio"
+ "bytes"
"crypto/tls"
"fmt"
"io"
)
func TestNextProtoUpgrade(t *testing.T) {
+ defer afterTest(t)
ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {
fmt.Fprintf(w, "path=%s,proto=", r.URL.Path)
if r.TLS != nil {
ts.StartTLS()
defer ts.Close()
- tr := newTLSTransport(t, ts)
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
-
// Normal request, without NPN.
{
+ tr := newTLSTransport(t, ts)
+ defer tr.CloseIdleConnections()
+ c := &Client{Transport: tr}
+
res, err := c.Get(ts.URL)
if err != nil {
t.Fatal(err)
// Request to an advertised but unhandled NPN protocol.
// Server will hang up.
{
- tr.CloseIdleConnections()
+ tr := newTLSTransport(t, ts)
tr.TLSClientConfig.NextProtos = []string{"unhandled-proto"}
- _, err := c.Get(ts.URL)
+ defer tr.CloseIdleConnections()
+ c := &Client{Transport: tr}
+
+ res, err := c.Get(ts.URL)
if err == nil {
- t.Errorf("expected error on unhandled-proto request")
+ defer res.Body.Close()
+ var buf bytes.Buffer
+ res.Write(&buf)
+ t.Errorf("expected error on unhandled-proto request; got: %s", buf.Bytes())
}
}
}
func TestRedirect(t *testing.T) {
+ defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
switch r.URL.Path {
case "/":
}
func TestConsumingBodyOnNextConn(t *testing.T) {
+ defer afterTest(t)
conn := new(testConn)
for i := 0; i < 2; i++ {
conn.readBuf.Write([]byte(
func (t *Transport) CancelRequest(req *Request) {
t.reqMu.Lock()
cancel := t.reqCanceler[req]
+ delete(t.reqCanceler, req)
t.reqMu.Unlock()
if cancel != nil {
cancel()
numExpectedResponses int
closed bool // whether conn has been closed
broken bool // an error has happened on this connection; marked broken so it's not reused.
+ canceled bool // whether this conn was broken due a CancelRequest
// mutateHeaderFunc is an optional func to modify extra
// headers on each outbound request before it's written. (the
// original Request given to RoundTrip is not modified)
return b
}
+// isCanceled reports whether this connection was closed due to CancelRequest.
+func (pc *persistConn) isCanceled() bool {
+ pc.lk.Lock()
+ defer pc.lk.Unlock()
+ return pc.canceled
+}
+
func (pc *persistConn) cancelRequest() {
- pc.conn.Close()
+ pc.lk.Lock()
+ defer pc.lk.Unlock()
+ pc.canceled = true
+ pc.closeLocked()
}
var remoteSideClosedFunc func(error) bool // or nil to use default
}
func (pc *persistConn) readLoop() {
- alive := true
+ // eofc is used to block http.Handler goroutines reading from Response.Body
+ // at EOF until this goroutines has (potentially) added the connection
+ // back to the idle pool.
+ eofc := make(chan struct{})
+ defer close(eofc) // unblock reader on errors
+ alive := true
for alive {
pb, err := pc.br.Peek(1)
alive = false
}
- var waitForBodyRead chan bool
+ var waitForBodyRead chan bool // channel is nil when there's no body
if hasBody {
waitForBodyRead = make(chan bool, 2)
resp.Body.(*bodyEOFSignal).earlyCloseFn = func() error {
- // Sending false here sets alive to
- // false and closes the connection
- // below.
waitForBodyRead <- false
return nil
}
- resp.Body.(*bodyEOFSignal).fn = func(err error) {
- waitForBodyRead <- alive &&
- err == nil &&
- !pc.sawEOF &&
- pc.wroteRequest() &&
- pc.t.putIdleConn(pc)
+ resp.Body.(*bodyEOFSignal).fn = func(err error) error {
+ isEOF := err == io.EOF
+ waitForBodyRead <- isEOF
+ if isEOF {
+ <-eofc // see comment at top
+ } else if err != nil && pc.isCanceled() {
+ return errRequestCanceled
+ }
+ return err
}
}
// on the response channel before erroring out.
rc.ch <- responseAndError{resp, err}
- if alive && !hasBody {
- alive = !pc.sawEOF &&
- pc.wroteRequest() &&
- pc.t.putIdleConn(pc)
- }
-
- // Wait for the just-returned response body to be fully consumed
- // before we race and peek on the underlying bufio reader.
- if waitForBodyRead != nil {
+ if hasBody {
+ // To avoid a race, wait for the just-returned
+ // response body to be fully consumed before peek on
+ // the underlying bufio reader.
select {
- case alive = <-waitForBodyRead:
+ case bodyEOF := <-waitForBodyRead:
+ pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
+ alive = alive &&
+ bodyEOF &&
+ !pc.sawEOF &&
+ pc.wroteRequest() &&
+ pc.t.putIdleConn(pc)
+ if bodyEOF {
+ eofc <- struct{}{}
+ }
case <-pc.closech:
alive = false
}
- }
-
- pc.t.setReqCanceler(rc.req, nil)
-
- if !alive {
- pc.close()
+ } else {
+ pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool
+ alive = alive &&
+ !pc.sawEOF &&
+ pc.wroteRequest() &&
+ pc.t.putIdleConn(pc)
}
}
+ pc.close()
}
func (pc *persistConn) writeLoop() {
var errTimeout error = &httpError{err: "net/http: timeout awaiting response headers", timeout: true}
var errClosed error = &httpError{err: "net/http: transport closed before response was received"}
+var errRequestCanceled = errors.New("net/http: request canceled")
var testHookPersistConnClosedGotRes func() // nil except for tests
// bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most
// once, right before its final (error-producing) Read or Close call
-// returns. If earlyCloseFn is non-nil and Close is called before
-// io.EOF is seen, earlyCloseFn is called instead of fn, and its
-// return value is the return value from Close.
+// returns. fn should return the new error to return from Read or Close.
+//
+// If earlyCloseFn is non-nil and Close is called before io.EOF is
+// seen, earlyCloseFn is called instead of fn, and its return value is
+// the return value from Close.
type bodyEOFSignal struct {
body io.ReadCloser
- mu sync.Mutex // guards following 4 fields
- closed bool // whether Close has been called
- rerr error // sticky Read error
- fn func(error) // error will be nil on Read io.EOF
- earlyCloseFn func() error // optional alt Close func used if io.EOF not seen
+ mu sync.Mutex // guards following 4 fields
+ closed bool // whether Close has been called
+ rerr error // sticky Read error
+ fn func(error) error // err will be nil on Read io.EOF
+ earlyCloseFn func() error // optional alt Close func used if io.EOF not seen
}
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
if es.rerr == nil {
es.rerr = err
}
- es.condfn(err)
+ err = es.condfn(err)
}
return
}
return es.earlyCloseFn()
}
err := es.body.Close()
- es.condfn(err)
- return err
+ return es.condfn(err)
}
// caller must hold es.mu.
-func (es *bodyEOFSignal) condfn(err error) {
+func (es *bodyEOFSignal) condfn(err error) error {
if es.fn == nil {
- return
- }
- if err == io.EOF {
- err = nil
+ return err
}
- es.fn(err)
+ err = es.fn(err)
es.fn = nil
+ return err
}
// gzipReader wraps a response body so it can lazily
tr := &Transport{DisableKeepAlives: false}
c := &Client{Transport: tr}
+ defer tr.CloseIdleConnections()
// Do a bunch of traffic from different goroutines. Send to activityc
// after each request completes, regardless of whether it failed.
+ // If these are too high, OS X exhausts its emphemeral ports
+ // and hangs waiting for them to transition TCP states. That's
+ // not what we want to test. TODO(bradfitz): use an io.Pipe
+ // dialer for this test instead?
const (
- numClients = 50
- reqsPerClient = 250
+ numClients = 20
+ reqsPerClient = 25
)
activityc := make(chan bool)
for i := 0; i < numClients; i++ {
body, err := ioutil.ReadAll(res.Body)
d := time.Since(t0)
- if err == nil {
- t.Error("expected an error reading the body")
+ if err != ExportErrRequestCanceled {
+ t.Errorf("Body.Read error = %v; want errRequestCanceled", err)
}
if string(body) != "Hello" {
t.Errorf("Body = %q; want Hello", body)
}
// Verify no outstanding requests after readLoop/writeLoop
// goroutines shut down.
- for tries := 3; tries > 0; tries-- {
+ for tries := 5; tries > 0; tries-- {
n := tr.NumPendingRequestsForTesting()
if n == 0 {
break
eventLog.Printf("canceling")
tr.CancelRequest(req)
+ tr.CancelRequest(req) // used to panic on second call
select {
case <-gotres:
}
}
+// Test for issue 10474
+func TestTransportResponseCancelRace(t *testing.T) {
+ defer afterTest(t)
+
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ // important that this response has a body.
+ var b [1024]byte
+ w.Write(b[:])
+ }))
+ defer ts.Close()
+
+ tr := &Transport{}
+ defer tr.CloseIdleConnections()
+
+ req, err := NewRequest("GET", ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // If we do an early close, Transport just throws the connection away and
+ // doesn't reuse it. In order to trigger the bug, it has to reuse the connection
+ // so read the body
+ if _, err := io.Copy(ioutil.Discard, res.Body); err != nil {
+ t.Fatal(err)
+ }
+
+ req2, err := NewRequest("GET", ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tr.CancelRequest(req)
+ res, err = tr.RoundTrip(req2)
+ if err != nil {
+ t.Fatal(err)
+ }
+ res.Body.Close()
+}
+
func wantBody(res *http.Response, err error, want string) error {
if err != nil {
return err