Skip to content

Commit a3f7be9

Browse files
author
Bryan C. Mills
committed
net/http: reduce TestClientTimeout_h{1,2} latency
The test had been setting an arbitrary 200ms timeout to allow the server's handler to set up before timing out. That is not only potentially flaky on slow machines, but also typically much longer than necessary. Replace the hard-coded timeout with a much shorter initial timeout, and use exponential backoff to lengthen it if needed. This allows the test to be run about 20x faster in the typical case, which may make it easier to reproduce rare failure modes by running with a higher -count flag. For #43120 Change-Id: I1e0d0ec99d5a107fff56e3bcc7174d686ec582d1 Reviewed-on: https://go-review.googlesource.com/c/go/+/361275 Trust: Bryan C. Mills <bcmills@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Damien Neil <dneil@google.com>
1 parent 2622235 commit a3f7be9

File tree

1 file changed

+58
-42
lines changed

1 file changed

+58
-42
lines changed

src/net/http/client_test.go

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,64 +1205,80 @@ func TestClientTimeout_h2(t *testing.T) { testClientTimeout(t, h2Mode) }
12051205
func testClientTimeout(t *testing.T, h2 bool) {
12061206
setParallel(t)
12071207
defer afterTest(t)
1208-
testDone := make(chan struct{}) // closed in defer below
12091208

1210-
sawRoot := make(chan bool, 1)
1211-
sawSlow := make(chan bool, 1)
1209+
var (
1210+
mu sync.Mutex
1211+
nonce string // a unique per-request string
1212+
sawSlowNonce bool // true if the handler saw /slow?nonce=<nonce>
1213+
)
12121214
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
1215+
_ = r.ParseForm()
12131216
if r.URL.Path == "/" {
1214-
sawRoot <- true
1215-
Redirect(w, r, "/slow", StatusFound)
1217+
Redirect(w, r, "/slow?nonce="+r.Form.Get("nonce"), StatusFound)
12161218
return
12171219
}
12181220
if r.URL.Path == "/slow" {
1219-
sawSlow <- true
1221+
mu.Lock()
1222+
if r.Form.Get("nonce") == nonce {
1223+
sawSlowNonce = true
1224+
} else {
1225+
t.Logf("mismatched nonce: received %s, want %s", r.Form.Get("nonce"), nonce)
1226+
}
1227+
mu.Unlock()
1228+
12201229
w.Write([]byte("Hello"))
12211230
w.(Flusher).Flush()
1222-
<-testDone
1231+
<-r.Context().Done()
12231232
return
12241233
}
12251234
}))
12261235
defer cst.close()
1227-
defer close(testDone) // before cst.close, to unblock /slow handler
12281236

1229-
// 200ms should be long enough to get a normal request (the /
1230-
// handler), but not so long that it makes the test slow.
1231-
const timeout = 200 * time.Millisecond
1232-
cst.c.Timeout = timeout
1233-
1234-
res, err := cst.c.Get(cst.ts.URL)
1235-
if err != nil {
1236-
if strings.Contains(err.Error(), "Client.Timeout") {
1237-
t.Skipf("host too slow to get fast resource in %v", timeout)
1237+
// Try to trigger a timeout after reading part of the response body.
1238+
// The initial timeout is emprically usually long enough on a decently fast
1239+
// machine, but if we undershoot we'll retry with exponentially longer
1240+
// timeouts until the test either passes or times out completely.
1241+
// This keeps the test reasonably fast in the typical case but allows it to
1242+
// also eventually succeed on arbitrarily slow machines.
1243+
timeout := 10 * time.Millisecond
1244+
nextNonce := 0
1245+
for ; ; timeout *= 2 {
1246+
if timeout <= 0 {
1247+
// The only way we can feasibly hit this while the test is running is if
1248+
// the request fails without actually waiting for the timeout to occur.
1249+
t.Fatalf("timeout overflow")
1250+
}
1251+
if deadline, ok := t.Deadline(); ok && !time.Now().Add(timeout).Before(deadline) {
1252+
t.Fatalf("failed to produce expected timeout before test deadline")
1253+
}
1254+
t.Logf("attempting test with timeout %v", timeout)
1255+
cst.c.Timeout = timeout
1256+
1257+
mu.Lock()
1258+
nonce = fmt.Sprint(nextNonce)
1259+
nextNonce++
1260+
sawSlowNonce = false
1261+
mu.Unlock()
1262+
res, err := cst.c.Get(cst.ts.URL + "/?nonce=" + nonce)
1263+
if err != nil {
1264+
if strings.Contains(err.Error(), "Client.Timeout") {
1265+
// Timed out before handler could respond.
1266+
t.Logf("timeout before response received")
1267+
continue
1268+
}
1269+
t.Fatal(err)
12381270
}
1239-
t.Fatal(err)
1240-
}
12411271

1242-
select {
1243-
case <-sawRoot:
1244-
// good.
1245-
default:
1246-
t.Fatal("handler never got / request")
1247-
}
1248-
1249-
select {
1250-
case <-sawSlow:
1251-
// good.
1252-
default:
1253-
t.Fatal("handler never got /slow request")
1254-
}
1272+
mu.Lock()
1273+
ok := sawSlowNonce
1274+
mu.Unlock()
1275+
if !ok {
1276+
t.Fatal("handler never got /slow request, but client returned response")
1277+
}
12551278

1256-
errc := make(chan error, 1)
1257-
go func() {
1258-
_, err := io.ReadAll(res.Body)
1259-
errc <- err
1279+
_, err = io.ReadAll(res.Body)
12601280
res.Body.Close()
1261-
}()
12621281

1263-
const failTime = 5 * time.Second
1264-
select {
1265-
case err := <-errc:
12661282
if err == nil {
12671283
t.Fatal("expected error from ReadAll")
12681284
}
@@ -1275,8 +1291,8 @@ func testClientTimeout(t *testing.T, h2 bool) {
12751291
if got := ne.Error(); !strings.Contains(got, "(Client.Timeout") {
12761292
t.Errorf("error string = %q; missing timeout substring", got)
12771293
}
1278-
case <-time.After(failTime):
1279-
t.Errorf("timeout after %v waiting for timeout of %v", failTime, timeout)
1294+
1295+
break
12801296
}
12811297
}
12821298

0 commit comments

Comments
 (0)