mirror of
https://github.com/golang/go
synced 2024-11-18 16:54:43 -07:00
net/http: fix scheduling race resulting in flaky test
The test was measuring something, assuming other goroutines had already scheduled. Fixes #10427 Change-Id: I2a4d3906f9d4b5ea44b57d972e303bbe2b0b1cde Reviewed-on: https://go-review.googlesource.com/9561 Reviewed-by: David Crawshaw <crawshaw@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
80cedf3e8f
commit
c723230e4a
@ -10,9 +10,17 @@ package http
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// We only want to pay for this cost during testing.
|
||||||
|
// When not under test, these values are always nil
|
||||||
|
// and never assigned to.
|
||||||
|
testHookMu = new(sync.Mutex)
|
||||||
|
}
|
||||||
|
|
||||||
func NewLoggingConn(baseName string, c net.Conn) net.Conn {
|
func NewLoggingConn(baseName string, c net.Conn) net.Conn {
|
||||||
return newLoggingConn(baseName, c)
|
return newLoggingConn(baseName, c)
|
||||||
}
|
}
|
||||||
@ -86,6 +94,12 @@ func SetEnterRoundTripHook(f func()) {
|
|||||||
testHookEnterRoundTrip = f
|
testHookEnterRoundTrip = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetReadLoopBeforeNextReadHook(f func()) {
|
||||||
|
testHookMu.Lock()
|
||||||
|
defer testHookMu.Unlock()
|
||||||
|
testHookReadLoopBeforeNextRead = f
|
||||||
|
}
|
||||||
|
|
||||||
func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler {
|
func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler {
|
||||||
f := func() <-chan time.Time {
|
f := func() <-chan time.Time {
|
||||||
return ch
|
return ch
|
||||||
|
@ -877,6 +877,11 @@ func (pc *persistConn) readLoop() {
|
|||||||
eofc := make(chan struct{})
|
eofc := make(chan struct{})
|
||||||
defer close(eofc) // unblock reader on errors
|
defer close(eofc) // unblock reader on errors
|
||||||
|
|
||||||
|
// Read this once, before loop starts. (to avoid races in tests)
|
||||||
|
testHookMu.Lock()
|
||||||
|
testHookReadLoopBeforeNextRead := testHookReadLoopBeforeNextRead
|
||||||
|
testHookMu.Unlock()
|
||||||
|
|
||||||
alive := true
|
alive := true
|
||||||
for alive {
|
for alive {
|
||||||
pb, err := pc.br.Peek(1)
|
pb, err := pc.br.Peek(1)
|
||||||
@ -993,6 +998,10 @@ func (pc *persistConn) readLoop() {
|
|||||||
pc.wroteRequest() &&
|
pc.wroteRequest() &&
|
||||||
pc.t.putIdleConn(pc)
|
pc.t.putIdleConn(pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hook := testHookReadLoopBeforeNextRead; hook != nil {
|
||||||
|
hook()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pc.close()
|
pc.close()
|
||||||
}
|
}
|
||||||
@ -1090,6 +1099,8 @@ var errRequestCanceled = errors.New("net/http: request canceled")
|
|||||||
var (
|
var (
|
||||||
testHookPersistConnClosedGotRes func()
|
testHookPersistConnClosedGotRes func()
|
||||||
testHookEnterRoundTrip func()
|
testHookEnterRoundTrip func()
|
||||||
|
testHookMu sync.Locker = fakeLocker{} // guards following
|
||||||
|
testHookReadLoopBeforeNextRead func()
|
||||||
)
|
)
|
||||||
|
|
||||||
func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
|
func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
|
||||||
@ -1344,3 +1355,11 @@ func (nr noteEOFReader) Read(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fakeLocker is a sync.Locker which does nothing. It's used to guard
|
||||||
|
// test-only fields when not under test, to avoid runtime atomic
|
||||||
|
// overhead.
|
||||||
|
type fakeLocker struct{}
|
||||||
|
|
||||||
|
func (fakeLocker) Lock() {}
|
||||||
|
func (fakeLocker) Unlock() {}
|
||||||
|
@ -1827,6 +1827,11 @@ func TestIdleConnChannelLeak(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
|
const nReqs = 5
|
||||||
|
didRead := make(chan bool, nReqs)
|
||||||
|
SetReadLoopBeforeNextReadHook(func() { didRead <- true })
|
||||||
|
defer SetReadLoopBeforeNextReadHook(nil)
|
||||||
|
|
||||||
tr := &Transport{
|
tr := &Transport{
|
||||||
Dial: func(netw, addr string) (net.Conn, error) {
|
Dial: func(netw, addr string) (net.Conn, error) {
|
||||||
return net.Dial(netw, ts.Listener.Addr().String())
|
return net.Dial(netw, ts.Listener.Addr().String())
|
||||||
@ -1839,12 +1844,28 @@ func TestIdleConnChannelLeak(t *testing.T) {
|
|||||||
// First, without keep-alives.
|
// First, without keep-alives.
|
||||||
for _, disableKeep := range []bool{true, false} {
|
for _, disableKeep := range []bool{true, false} {
|
||||||
tr.DisableKeepAlives = disableKeep
|
tr.DisableKeepAlives = disableKeep
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < nReqs; i++ {
|
||||||
_, err := c.Get(fmt.Sprintf("http://foo-host-%d.tld/", i))
|
_, err := c.Get(fmt.Sprintf("http://foo-host-%d.tld/", i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// Note: no res.Body.Close is needed here, since the
|
||||||
|
// response Content-Length is zero. Perhaps the test
|
||||||
|
// should be more explicit and use a HEAD, but tests
|
||||||
|
// elsewhere guarantee that zero byte responses generate
|
||||||
|
// a "Content-Length: 0" instead of chunking.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point, each of the 5 Transport.readLoop goroutines
|
||||||
|
// are scheduling noting that there are no response bodies (see
|
||||||
|
// earlier comment), and are then calling putIdleConn, which
|
||||||
|
// decrements this count. Usually that happens quickly, which is
|
||||||
|
// why this test has seemed to work for ages. But it's still
|
||||||
|
// racey: we have wait for them to finish first. See Issue 10427
|
||||||
|
for i := 0; i < nReqs; i++ {
|
||||||
|
<-didRead
|
||||||
|
}
|
||||||
|
|
||||||
if got := tr.IdleConnChMapSizeForTesting(); got != 0 {
|
if got := tr.IdleConnChMapSizeForTesting(); got != 0 {
|
||||||
t.Fatalf("ForDisableKeepAlives = %v, map size = %d; want 0", disableKeep, got)
|
t.Fatalf("ForDisableKeepAlives = %v, map size = %d; want 0", disableKeep, got)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user