aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Thorogood <me+google@tomthorogood.co.uk>2019-09-14 01:16:20 +0000
committerBrad Fitzpatrick <bradfitz@golang.org>2019-09-25 15:48:22 +0000
commita055bb937046f341ab74a7a471dc79e9e6b58af2 (patch)
tree290042520c9d4adc3d527c8c886cecdc1a7b69a1
parentfb86bbb3155340860720ba6cba37cc0f18536796 (diff)
downloadgo-a055bb937046f341ab74a7a471dc79e9e6b58af2.tar.gz
go-a055bb937046f341ab74a7a471dc79e9e6b58af2.zip
[release-branch.go1.13] 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 #34285 Change-Id: If54bfaf6edb14f5391463f908efbef5bb8a5d78e GitHub-Last-Rev: 2b7d66a1ce66b4424c4d0fca2b8e8b547d874136 GitHub-Pull-Request: golang/go#34283 Reviewed-on: https://go-review.googlesource.com/c/go/+/195237 Reviewed-by: Michael Fraenkel <michael.fraenkel@gmail.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> (cherry picked from commit 582d5194faec20c475ab93b45cf0520253dec4a9) Reviewed-on: https://go-review.googlesource.com/c/go/+/196579 Reviewed-by: Daniel Martí <mvdan@mvdan.cc> Reviewed-by: Bryan C. Mills <bcmills@google.com> Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
-rw-r--r--src/net/http/export_test.go24
-rw-r--r--src/net/http/transport.go4
-rw-r--r--src/net/http/transport_test.go32
3 files changed, 59 insertions, 1 deletions
diff --git a/src/net/http/export_test.go b/src/net/http/export_test.go
index d265cd3f72..e5c06a8903 100644
--- a/src/net/http/export_test.go
+++ b/src/net/http/export_test.go
@@ -208,6 +208,30 @@ func (t *Transport) PutIdleTestConn(scheme, addr string) bool {
}) == nil
}
+// PutIdleTestConnH2 reports whether it was able to insert a fresh
+// HTTP/2 persistConn for scheme, addr into the idle connection pool.
+func (t *Transport) PutIdleTestConnH2(scheme, addr string, alt RoundTripper) bool {
+ key := connectMethodKey{"", scheme, addr, false}
+
+ if t.MaxConnsPerHost > 0 {
+ // Transport is tracking conns-per-host.
+ // Increment connection count to account
+ // for new persistConn created below.
+ t.connsPerHostMu.Lock()
+ if t.connsPerHost == nil {
+ t.connsPerHost = make(map[connectMethodKey]int)
+ }
+ t.connsPerHost[key]++
+ t.connsPerHostMu.Unlock()
+ }
+
+ return t.tryPutIdleConn(&persistConn{
+ t: t,
+ alt: alt,
+ cacheKey: key,
+ }) == nil
+}
+
// All test hooks must be non-nil so they can be called directly,
// but the tests use nil to mean hook disabled.
func unnilTestHook(f *func()) {
diff --git a/src/net/http/transport.go b/src/net/http/transport.go
index ee279877e0..9c62f0f779 100644
--- a/src/net/http/transport.go
+++ b/src/net/http/transport.go
@@ -1206,7 +1206,9 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
// Queue for idle connection.
if delivered := t.queueForIdleConn(w); delivered {
pc := w.pc
- if trace != nil && trace.GotConn != nil {
+ // Trace only for HTTP/1.
+ // HTTP/2 calls trace.GotConn itself.
+ if pc.alt == nil && trace != nil && trace.GotConn != nil {
trace.GotConn(pc.gotIdleConnTrace(pc.idleAt))
}
// set request canceler to some non-nil function so we
diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go
index 23afff5d84..746c4c530e 100644
--- a/src/net/http/transport_test.go
+++ b/src/net/http/transport_test.go
@@ -3562,6 +3562,38 @@ func TestTransportCloseIdleConnsThenReturn(t *testing.T) {
wantIdle("after final put", 1)
}
+// Test for issue 34282
+// Ensure that getConn doesn't call the GotConn trace hook on a HTTP/2 idle conn
+func TestTransportTraceGotConnH2IdleConns(t *testing.T) {
+ tr := &Transport{}
+ wantIdle := func(when string, n int) bool {
+ got := tr.IdleConnCountForTesting("https", "example.com:443") // key used by PutIdleTestConnH2
+ if got == n {
+ return true
+ }
+ t.Errorf("%s: idle conns = %d; want %d", when, got, n)
+ return false
+ }
+ wantIdle("start", 0)
+ alt := funcRoundTripper(func() {})
+ if !tr.PutIdleTestConnH2("https", "example.com:443", alt) {
+ t.Fatal("put failed")
+ }
+ wantIdle("after put", 1)
+ ctx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{
+ GotConn: func(httptrace.GotConnInfo) {
+ // tr.getConn should leave it for the HTTP/2 alt to call GotConn.
+ t.Error("GotConn called")
+ },
+ })
+ req, _ := NewRequestWithContext(ctx, MethodGet, "https://example.com", nil)
+ _, err := tr.RoundTrip(req)
+ if err != errFakeRoundTrip {
+ t.Errorf("got error: %v; want %q", err, errFakeRoundTrip)
+ }
+ wantIdle("after round trip", 1)
+}
+
// This tests that an client requesting a content range won't also
// implicitly ask for gzip support. If they want that, they need to do it
// on their own.