Skip to content

Commit d06dfc7

Browse files
neilddmitshur
authored andcommitted
[internal-branch.go1.16-vendor] http2: avoid extra GetConn trace call
CL 352469 inverts the case in shouldTraceGetConn: We want to call GetConn for connections that have been previously used, but it calls GetConn only on approximately the first use. "Approximately", because it uses cc.nextStreamID to determine if the connection has been used, which is racy. Restructure the decision to call GetConn to track a per-ClientConn bool indicating whether GetConn has already been called for this connection. Set this bool for connections received from net/http, clear it after the first use of the connection. Updates golang/go#49076 Change-Id: I8e3dbba7cfbce9acd3612e39b6b6ee558bbfc864 Reviewed-on: https://go-review.googlesource.com/c/net/+/353875 Trust: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/net/+/357089 Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 1760f31 commit d06dfc7

File tree

3 files changed

+75
-32
lines changed

3 files changed

+75
-32
lines changed

http2/client_conn_pool.go

+7-25
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ type clientConnPool struct {
4848
conns map[string][]*ClientConn // key is host:port
4949
dialing map[string]*dialCall // currently in-flight dials
5050
keys map[*ClientConn][]string
51-
addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
51+
addConnCalls map[string]*addConnCall // in-flight addConnIfNeeded calls
5252
}
5353

5454
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
@@ -60,29 +60,6 @@ const (
6060
noDialOnMiss = false
6161
)
6262

63-
// shouldTraceGetConn reports whether getClientConn should call any
64-
// ClientTrace.GetConn hook associated with the http.Request.
65-
//
66-
// This complexity is needed to avoid double calls of the GetConn hook
67-
// during the back-and-forth between net/http and x/net/http2 (when the
68-
// net/http.Transport is upgraded to also speak http2), as well as support
69-
// the case where x/net/http2 is being used directly.
70-
func (p *clientConnPool) shouldTraceGetConn(cc *ClientConn) bool {
71-
// If our Transport wasn't made via ConfigureTransport, always
72-
// trace the GetConn hook if provided, because that means the
73-
// http2 package is being used directly and it's the one
74-
// dialing, as opposed to net/http.
75-
if _, ok := p.t.ConnPool.(noDialClientConnPool); !ok {
76-
return true
77-
}
78-
// Otherwise, only use the GetConn hook if this connection has
79-
// been used previously for other requests. For fresh
80-
// connections, the net/http package does the dialing.
81-
cc.mu.Lock()
82-
defer cc.mu.Unlock()
83-
return cc.nextStreamID == 1
84-
}
85-
8663
func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
8764
// TODO(dneil): Dial a new connection when t.DisableKeepAlives is set?
8865
if isConnectionCloseRequest(req) && dialOnMiss {
@@ -99,9 +76,13 @@ func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMis
9976
p.mu.Lock()
10077
for _, cc := range p.conns[addr] {
10178
if cc.ReserveNewRequest() {
102-
if p.shouldTraceGetConn(cc) {
79+
// When a connection is presented to us by the net/http package,
80+
// the GetConn hook has already been called.
81+
// Don't call it a second time here.
82+
if !cc.getConnCalled {
10383
traceGetConn(req, addr)
10484
}
85+
cc.getConnCalled = false
10586
p.mu.Unlock()
10687
return cc, nil
10788
}
@@ -214,6 +195,7 @@ type addConnCall struct {
214195

215196
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
216197
cc, err := t.NewClientConn(tc)
198+
cc.getConnCalled = true // already called by the net/http package
217199

218200
p := c.p
219201
p.mu.Lock()

http2/transport.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,12 @@ func (t *Transport) initConnPool() {
236236
// ClientConn is the state of a single HTTP/2 client connection to an
237237
// HTTP/2 server.
238238
type ClientConn struct {
239-
t *Transport
240-
tconn net.Conn // usually *tls.Conn, except specialized impls
241-
tlsState *tls.ConnectionState // nil only for specialized impls
242-
reused uint32 // whether conn is being reused; atomic
243-
singleUse bool // whether being used for a single http.Request
239+
t *Transport
240+
tconn net.Conn // usually *tls.Conn, except specialized impls
241+
tlsState *tls.ConnectionState // nil only for specialized impls
242+
reused uint32 // whether conn is being reused; atomic
243+
singleUse bool // whether being used for a single http.Request
244+
getConnCalled bool // used by clientConnPool
244245

245246
// readLoop goroutine fields:
246247
readerDone chan struct{} // closed on error
@@ -757,7 +758,6 @@ func (cc *ClientConn) ReserveNewRequest() bool {
757758
// connection to initiate a new RoundTrip request.
758759
type clientConnIdleState struct {
759760
canTakeNewRequest bool
760-
freshConn bool // whether it's unused by any previous request
761761
}
762762

763763
func (cc *ClientConn) idleState() clientConnIdleState {
@@ -785,7 +785,6 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
785785
!cc.doNotReuse &&
786786
int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
787787
!cc.tooIdleLocked()
788-
st.freshConn = cc.nextStreamID == 1 && st.canTakeNewRequest
789788
return
790789
}
791790

http2/transport_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,68 @@ func TestTransportReusesConn_ConnClose(t *testing.T) {
243243
}
244244
}
245245

246+
func TestTransportGetGotConnHooks_HTTP2Transport(t *testing.T) {
247+
testTransportGetGotConnHooks(t, false)
248+
}
249+
func TestTransportGetGotConnHooks_Client(t *testing.T) { testTransportGetGotConnHooks(t, true) }
250+
251+
func testTransportGetGotConnHooks(t *testing.T, useClient bool) {
252+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
253+
io.WriteString(w, r.RemoteAddr)
254+
}, func(s *httptest.Server) {
255+
s.EnableHTTP2 = true
256+
}, optOnlyServer)
257+
defer st.Close()
258+
259+
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
260+
client := st.ts.Client()
261+
ConfigureTransports(client.Transport.(*http.Transport))
262+
263+
var (
264+
getConns int32
265+
gotConns int32
266+
)
267+
for i := 0; i < 2; i++ {
268+
trace := &httptrace.ClientTrace{
269+
GetConn: func(hostport string) {
270+
atomic.AddInt32(&getConns, 1)
271+
},
272+
GotConn: func(connInfo httptrace.GotConnInfo) {
273+
got := atomic.AddInt32(&gotConns, 1)
274+
wantReused, wantWasIdle := false, false
275+
if got > 1 {
276+
wantReused, wantWasIdle = true, true
277+
}
278+
if connInfo.Reused != wantReused || connInfo.WasIdle != wantWasIdle {
279+
t.Errorf("GotConn %v: Reused=%v (want %v), WasIdle=%v (want %v)", i, connInfo.Reused, wantReused, connInfo.WasIdle, wantWasIdle)
280+
}
281+
},
282+
}
283+
req, err := http.NewRequest("GET", st.ts.URL, nil)
284+
if err != nil {
285+
t.Fatal(err)
286+
}
287+
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
288+
289+
var res *http.Response
290+
if useClient {
291+
res, err = client.Do(req)
292+
} else {
293+
res, err = tr.RoundTrip(req)
294+
}
295+
if err != nil {
296+
t.Fatal(err)
297+
}
298+
res.Body.Close()
299+
if get := atomic.LoadInt32(&getConns); get != int32(i+1) {
300+
t.Errorf("after request %v, %v calls to GetConns: want %v", i, get, i+1)
301+
}
302+
if got := atomic.LoadInt32(&gotConns); got != int32(i+1) {
303+
t.Errorf("after request %v, %v calls to GotConns: want %v", i, got, i+1)
304+
}
305+
}
306+
}
307+
246308
// Tests that the Transport only keeps one pending dial open per destination address.
247309
// https://golang.org/issue/13397
248310
func TestTransportGroupsPendingDials(t *testing.T) {

0 commit comments

Comments
 (0)