Skip to content

Commit 582d519

Browse files
tmthrgdbradfitz
authored andcommitted
net/http: fix HTTP/2 idle pool tracing
CL 140357 caused HTTP/2 connections to be put in the idle pool, but failed to properly guard the trace.GotConn call in getConn. dialConn returns a minimal persistConn with conn == nil for HTTP/2 connections. This persistConn was then returned from queueForIdleConn and caused the httptrace.GotConnInfo passed into GotConn to have a nil Conn field. HTTP/2 connections call GotConn themselves so leave it for HTTP/2 to call GotConn as is done directly below. Fixes #34282 Change-Id: If54bfaf6edb14f5391463f908efbef5bb8a5d78e GitHub-Last-Rev: 2b7d66a GitHub-Pull-Request: #34283 Reviewed-on: https://go-review.googlesource.com/c/go/+/195237 Reviewed-by: Michael Fraenkel <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 45873a2 commit 582d519

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-1
lines changed

src/net/http/export_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,30 @@ func (t *Transport) PutIdleTestConn(scheme, addr string) bool {
208208
}) == nil
209209
}
210210

211+
// PutIdleTestConnH2 reports whether it was able to insert a fresh
212+
// HTTP/2 persistConn for scheme, addr into the idle connection pool.
213+
func (t *Transport) PutIdleTestConnH2(scheme, addr string, alt RoundTripper) bool {
214+
key := connectMethodKey{"", scheme, addr, false}
215+
216+
if t.MaxConnsPerHost > 0 {
217+
// Transport is tracking conns-per-host.
218+
// Increment connection count to account
219+
// for new persistConn created below.
220+
t.connsPerHostMu.Lock()
221+
if t.connsPerHost == nil {
222+
t.connsPerHost = make(map[connectMethodKey]int)
223+
}
224+
t.connsPerHost[key]++
225+
t.connsPerHostMu.Unlock()
226+
}
227+
228+
return t.tryPutIdleConn(&persistConn{
229+
t: t,
230+
alt: alt,
231+
cacheKey: key,
232+
}) == nil
233+
}
234+
211235
// All test hooks must be non-nil so they can be called directly,
212236
// but the tests use nil to mean hook disabled.
213237
func unnilTestHook(f *func()) {

src/net/http/transport.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,9 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
11951195
// Queue for idle connection.
11961196
if delivered := t.queueForIdleConn(w); delivered {
11971197
pc := w.pc
1198-
if trace != nil && trace.GotConn != nil {
1198+
// Trace only for HTTP/1.
1199+
// HTTP/2 calls trace.GotConn itself.
1200+
if pc.alt == nil && trace != nil && trace.GotConn != nil {
11991201
trace.GotConn(pc.gotIdleConnTrace(pc.idleAt))
12001202
}
12011203
// set request canceler to some non-nil function so we

src/net/http/transport_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3562,6 +3562,38 @@ func TestTransportCloseIdleConnsThenReturn(t *testing.T) {
35623562
wantIdle("after final put", 1)
35633563
}
35643564

3565+
// Test for issue 34282
3566+
// Ensure that getConn doesn't call the GotConn trace hook on a HTTP/2 idle conn
3567+
func TestTransportTraceGotConnH2IdleConns(t *testing.T) {
3568+
tr := &Transport{}
3569+
wantIdle := func(when string, n int) bool {
3570+
got := tr.IdleConnCountForTesting("https", "example.com:443") // key used by PutIdleTestConnH2
3571+
if got == n {
3572+
return true
3573+
}
3574+
t.Errorf("%s: idle conns = %d; want %d", when, got, n)
3575+
return false
3576+
}
3577+
wantIdle("start", 0)
3578+
alt := funcRoundTripper(func() {})
3579+
if !tr.PutIdleTestConnH2("https", "example.com:443", alt) {
3580+
t.Fatal("put failed")
3581+
}
3582+
wantIdle("after put", 1)
3583+
ctx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{
3584+
GotConn: func(httptrace.GotConnInfo) {
3585+
// tr.getConn should leave it for the HTTP/2 alt to call GotConn.
3586+
t.Error("GotConn called")
3587+
},
3588+
})
3589+
req, _ := NewRequestWithContext(ctx, MethodGet, "https://example.com", nil)
3590+
_, err := tr.RoundTrip(req)
3591+
if err != errFakeRoundTrip {
3592+
t.Errorf("got error: %v; want %q", err, errFakeRoundTrip)
3593+
}
3594+
wantIdle("after round trip", 1)
3595+
}
3596+
35653597
// This tests that an client requesting a content range won't also
35663598
// implicitly ask for gzip support. If they want that, they need to do it
35673599
// on their own.

0 commit comments

Comments
 (0)