Skip to content

Commit

Permalink
net/http: wait forever for write results in tests
Browse files Browse the repository at this point in the history
After performing a round trip on a connection, the connection is
usually returned to the idle connection pool. If the write of the
request did not complete successfully, the connection is not
returned.

It is possible for the response to be read before the write
goroutine has finished signalling that its write has completed.
To allow for this, the check to see if the write completed successfully
waits for 50ms for the write goroutine to report the result of the
write.

See comments in persistConn.wroteRequest for more details.

On a slow builder, it is possible for the write goroutine to take
longer than 50ms to report the status of its write, leading to test
flakiness when successive requests unexpectedly use different connections.

Set the timeout for waiting for the writer to an effectively
infinite duration in tests.

Fixes golang#51147
Fixes golang#56275
Fixes golang#56419
Fixes golang#56577
Fixes golang#57375
Fixes golang#57417
Fixes golang#57476
Fixes golang#57604
Fixes golang#57605

Change-Id: I5e92ffd66b676f3f976d8832c0910f27456a6991
Reviewed-on: https://go-review.googlesource.com/c/go/+/483116
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
Run-TryBot: Bryan Mills <[email protected]>
  • Loading branch information
neild committed Apr 7, 2023
1 parent 38dadcc commit 1444c0b
Show file tree
Hide file tree
Showing 4 changed files with 13 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/net/http/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var (
Export_is408Message = is408Message
)

const MaxWriteWaitBeforeConnReuse = maxWriteWaitBeforeConnReuse
var MaxWriteWaitBeforeConnReuse = &maxWriteWaitBeforeConnReuse

func init() {
// We only want to pay for this cost during testing.
Expand Down
1 change: 1 addition & 0 deletions src/net/http/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
var quietLog = log.New(io.Discard, "", 0)

func TestMain(m *testing.M) {
*http.MaxWriteWaitBeforeConnReuse = 60 * time.Minute
v := m.Run()
if v == 0 && goroutineLeaked() {
os.Exit(1)
Expand Down
5 changes: 4 additions & 1 deletion src/net/http/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -2452,7 +2452,10 @@ func (pc *persistConn) writeLoop() {
// maxWriteWaitBeforeConnReuse is how long the a Transport RoundTrip
// will wait to see the Request's Body.Write result after getting a
// response from the server. See comments in (*persistConn).wroteRequest.
const maxWriteWaitBeforeConnReuse = 50 * time.Millisecond
//
// In tests, we set this to a large value to avoid flakiness from inconsistent
// recycling of connections.
var maxWriteWaitBeforeConnReuse = 50 * time.Millisecond

// wroteRequest is a check before recycling a connection that the previous write
// (from writeLoop above) happened and was successful.
Expand Down
10 changes: 7 additions & 3 deletions src/net/http/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3402,9 +3402,13 @@ func (c byteFromChanReader) Read(p []byte) (n int, err error) {
// questionable state.
// golang.org/issue/7569
func TestTransportNoReuseAfterEarlyResponse(t *testing.T) {
run(t, testTransportNoReuseAfterEarlyResponse, []testMode{http1Mode})
run(t, testTransportNoReuseAfterEarlyResponse, []testMode{http1Mode}, testNotParallel)
}
func testTransportNoReuseAfterEarlyResponse(t *testing.T, mode testMode) {
defer func(d time.Duration) {
*MaxWriteWaitBeforeConnReuse = d
}(*MaxWriteWaitBeforeConnReuse)
*MaxWriteWaitBeforeConnReuse = 10 * time.Millisecond
var sconn struct {
sync.Mutex
c net.Conn
Expand Down Expand Up @@ -3631,13 +3635,13 @@ func testRetryRequestsOnError(t *testing.T, mode testMode) {
req := tc.req()
res, err := c.Do(req)
if err != nil {
if time.Since(t0) < MaxWriteWaitBeforeConnReuse/2 {
if time.Since(t0) < *MaxWriteWaitBeforeConnReuse/2 {
mu.Lock()
got := logbuf.String()
mu.Unlock()
t.Fatalf("i=%d: Do = %v; log:\n%s", i, err, got)
}
t.Skipf("connection likely wasn't recycled within %d, interfering with actual test; skipping", MaxWriteWaitBeforeConnReuse)
t.Skipf("connection likely wasn't recycled within %d, interfering with actual test; skipping", *MaxWriteWaitBeforeConnReuse)
}
res.Body.Close()
if res.Request != req {
Expand Down

0 comments on commit 1444c0b

Please sign in to comment.