Commit ac055429 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: deflake TestRetryIdempotentRequestsOnError

The test was previously an integration test, relying on luck and many
goroutines and lots of time to hit the path to be tested.

Instead, rewrite the test to exactly hit the path to be tested, in one
try, in one goroutine.

Fixes #18205

Change-Id: I63cd513316344bfd7375dcc452c1c396dec0e49f
Reviewed-on: https://go-review.googlesource.com/35107
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent b842c9aa
...@@ -36,6 +36,7 @@ import ( ...@@ -36,6 +36,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"testing" "testing"
"time" "time"
) )
...@@ -2545,6 +2546,13 @@ type closerFunc func() error ...@@ -2545,6 +2546,13 @@ type closerFunc func() error
func (f closerFunc) Close() error { return f() } func (f closerFunc) Close() error { return f() }
type writerFuncConn struct {
net.Conn
write func(p []byte) (n int, err error)
}
func (c writerFuncConn) Write(p []byte) (n int, err error) { return c.write(p) }
// Issue 4677. If we try to reuse a connection that the server is in the // Issue 4677. If we try to reuse a connection that the server is in the
// process of closing, we may end up successfully writing out our request (or a // process of closing, we may end up successfully writing out our request (or a
// portion of our request) only to find a connection error when we try to read // portion of our request) only to find a connection error when we try to read
...@@ -2557,66 +2565,78 @@ func (f closerFunc) Close() error { return f() } ...@@ -2557,66 +2565,78 @@ func (f closerFunc) Close() error { return f() }
func TestRetryIdempotentRequestsOnError(t *testing.T) { func TestRetryIdempotentRequestsOnError(t *testing.T) {
defer afterTest(t) defer afterTest(t)
var (
mu sync.Mutex
logbuf bytes.Buffer
)
logf := func(format string, args ...interface{}) {
mu.Lock()
defer mu.Unlock()
fmt.Fprintf(&logbuf, format, args...)
logbuf.WriteByte('\n')
}
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
logf("Handler")
w.Header().Set("X-Status", "ok")
})) }))
defer ts.Close() defer ts.Close()
tr := &Transport{} var writeNumAtomic int32
tr := &Transport{
Dial: func(network, addr string) (net.Conn, error) {
logf("Dial")
c, err := net.Dial(network, ts.Listener.Addr().String())
if err != nil {
logf("Dial error: %v", err)
return nil, err
}
return &writerFuncConn{
Conn: c,
write: func(p []byte) (n int, err error) {
if atomic.AddInt32(&writeNumAtomic, 1) == 2 {
logf("intentional write failure")
return 0, errors.New("second write fails")
}
logf("Write(%q)", p)
return c.Write(p)
},
}, nil
},
}
defer tr.CloseIdleConnections()
c := &Client{Transport: tr} c := &Client{Transport: tr}
const N = 2
retryc := make(chan struct{}, N)
SetRoundTripRetried(func() { SetRoundTripRetried(func() {
retryc <- struct{}{} logf("Retried.")
}) })
defer SetRoundTripRetried(nil) defer SetRoundTripRetried(nil)
for n := 0; n < 100; n++ { for i := 0; i < 3; i++ {
// open 2 conns res, err := c.Get("http://fake.golang/")
errc := make(chan error, N) if err != nil {
for i := 0; i < N; i++ { t.Fatalf("i=%d: Get = %v", i, err)
// start goroutines, send on errc
go func() {
res, err := c.Get(ts.URL)
if err == nil {
res.Body.Close()
}
errc <- err
}()
}
for i := 0; i < N; i++ {
if err := <-errc; err != nil {
t.Fatal(err)
}
}
ts.CloseClientConnections()
for i := 0; i < N; i++ {
go func() {
res, err := c.Get(ts.URL)
if err == nil {
res.Body.Close()
}
errc <- err
}()
} }
res.Body.Close()
}
for i := 0; i < N; i++ { mu.Lock()
if err := <-errc; err != nil { got := logbuf.String()
t.Fatal(err) mu.Unlock()
} const want = `Dial
} Write("GET / HTTP/1.1\r\nHost: fake.golang\r\nUser-Agent: Go-http-client/1.1\r\nAccept-Encoding: gzip\r\n\r\n")
for i := 0; i < N; i++ { Handler
select { intentional write failure
case <-retryc: Retried.
// we triggered a retry, test was successful Dial
t.Logf("finished after %d runs\n", n) Write("GET / HTTP/1.1\r\nHost: fake.golang\r\nUser-Agent: Go-http-client/1.1\r\nAccept-Encoding: gzip\r\n\r\n")
return Handler
default: Write("GET / HTTP/1.1\r\nHost: fake.golang\r\nUser-Agent: Go-http-client/1.1\r\nAccept-Encoding: gzip\r\n\r\n")
} Handler
} `
if got != want {
t.Errorf("Log of events differs. Got:\n%s\nWant:\n%s", got, want)
} }
t.Fatal("did not trigger any retries")
} }
// Issue 6981 // Issue 6981
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment