Skip to content

Commit d8870b0

Browse files
committedMar 20, 2024
http2: use synthetic time in TestIdleConnTimeout
Rewrite TestIdleConnTimeout to use the new synthetic time and synchronization test facilities, rather than using real time and sleeps. Reduces the test time from 20 seconds to 0. Reduces all package tests on my laptop from 32 seconds to 12. Change-Id: I33838488168450a7acd6a462777b5a4caf7f5307 Reviewed-on: https://go-review.googlesource.com/c/net/+/572379 Reviewed-by: Jonathan Amsterdam <jba@google.com> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent d73acff commit d8870b0

File tree

2 files changed

+57
-37
lines changed

2 files changed

+57
-37
lines changed
 

‎http2/transport.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ type ClientConn struct {
310310
readerErr error // set before readerDone is closed
311311

312312
idleTimeout time.Duration // or 0 for never
313-
idleTimer *time.Timer
313+
idleTimer timer
314314

315315
mu sync.Mutex // guards following
316316
cond *sync.Cond // hold mu; broadcast on flow/closed changes
@@ -828,7 +828,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo
828828
}
829829
if d := t.idleConnTimeout(); d != 0 {
830830
cc.idleTimeout = d
831-
cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
831+
cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
832832
}
833833
if VerboseLogs {
834834
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())

‎http2/transport_test.go

+55-35
Original file line numberDiff line numberDiff line change
@@ -97,63 +97,83 @@ func startH2cServer(t *testing.T) net.Listener {
9797

9898
func TestIdleConnTimeout(t *testing.T) {
9999
for _, test := range []struct {
100+
name string
100101
idleConnTimeout time.Duration
101102
wait time.Duration
102103
baseTransport *http.Transport
103-
wantConns int32
104+
wantNewConn bool
104105
}{{
106+
name: "NoExpiry",
105107
idleConnTimeout: 2 * time.Second,
106108
wait: 1 * time.Second,
107109
baseTransport: nil,
108-
wantConns: 1,
110+
wantNewConn: false,
109111
}, {
112+
name: "H2TransportTimeoutExpires",
110113
idleConnTimeout: 1 * time.Second,
111114
wait: 2 * time.Second,
112115
baseTransport: nil,
113-
wantConns: 5,
116+
wantNewConn: true,
114117
}, {
118+
name: "H1TransportTimeoutExpires",
115119
idleConnTimeout: 0 * time.Second,
116120
wait: 1 * time.Second,
117121
baseTransport: &http.Transport{
118122
IdleConnTimeout: 2 * time.Second,
119123
},
120-
wantConns: 1,
124+
wantNewConn: false,
121125
}} {
122-
var gotConns int32
123-
124-
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
125-
io.WriteString(w, r.RemoteAddr)
126-
}, optOnlyServer)
127-
defer st.Close()
126+
t.Run(test.name, func(t *testing.T) {
127+
tt := newTestTransport(t, func(tr *Transport) {
128+
tr.IdleConnTimeout = test.idleConnTimeout
129+
})
130+
var tc *testClientConn
131+
for i := 0; i < 3; i++ {
132+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
133+
rt := tt.roundTrip(req)
134+
135+
// This request happens on a new conn if it's the first request
136+
// (and there is no cached conn), or if the test timeout is long
137+
// enough that old conns are being closed.
138+
wantConn := i == 0 || test.wantNewConn
139+
if has := tt.hasConn(); has != wantConn {
140+
t.Fatalf("request %v: hasConn=%v, want %v", i, has, wantConn)
141+
}
142+
if wantConn {
143+
tc = tt.getConn()
144+
// Read client's SETTINGS and first WINDOW_UPDATE,
145+
// send our SETTINGS.
146+
tc.wantFrameType(FrameSettings)
147+
tc.wantFrameType(FrameWindowUpdate)
148+
tc.writeSettings()
149+
}
150+
if tt.hasConn() {
151+
t.Fatalf("request %v: Transport has more than one conn", i)
152+
}
128153

129-
tr := &Transport{
130-
IdleConnTimeout: test.idleConnTimeout,
131-
TLSClientConfig: tlsConfigInsecure,
132-
}
133-
defer tr.CloseIdleConnections()
154+
// Respond to the client's request.
155+
hf := testClientConnReadFrame[*MetaHeadersFrame](tc)
156+
tc.writeHeaders(HeadersFrameParam{
157+
StreamID: hf.StreamID,
158+
EndHeaders: true,
159+
EndStream: true,
160+
BlockFragment: tc.makeHeaderBlockFragment(
161+
":status", "200",
162+
),
163+
})
164+
rt.wantStatus(200)
134165

135-
for i := 0; i < 5; i++ {
136-
req, _ := http.NewRequest("GET", st.ts.URL, http.NoBody)
137-
trace := &httptrace.ClientTrace{
138-
GotConn: func(connInfo httptrace.GotConnInfo) {
139-
if !connInfo.Reused {
140-
atomic.AddInt32(&gotConns, 1)
141-
}
142-
},
143-
}
144-
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
166+
// If this was a newly-accepted conn, read the SETTINGS ACK.
167+
if wantConn {
168+
tc.wantFrameType(FrameSettings) // ACK to our settings
169+
}
145170

146-
_, err := tr.RoundTrip(req)
147-
if err != nil {
148-
t.Fatalf("%v", err)
171+
tt.advance(test.wait)
172+
if got, want := tc.netConnClosed, test.wantNewConn; got != want {
173+
t.Fatalf("after waiting %v, conn closed=%v; want %v", test.wait, got, want)
174+
}
149175
}
150-
151-
<-time.After(test.wait)
152-
}
153-
154-
if gotConns != test.wantConns {
155-
t.Errorf("incorrect gotConns: %d != %d", gotConns, test.wantConns)
156-
}
176+
})
157177
}
158178
}
159179

0 commit comments

Comments
 (0)
Please sign in to comment.