Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9e95e4b

Browse files
committedAug 23, 2023
net/http: remove persistConn reference from wantConn
Transport getConn creates wantConn w, tries to obtain idle connection for it based on the w.key and, when there is no idle connection, puts wantConn into idleConnWait wantConnQueue. Then getConn dials connection for w in a goroutine and blocks. After dial succeeds getConn unblocks and returns connection to the caller. At this point w is stored in the idleConnWait and will not be evicted until another wantConn with the same w.key is requested or alive connection returned into the idle pool which may not happen e.g. if server closes the connection. The problem is that even after tryDeliver succeeds w references persistConn wrapper that allocates bufio.Reader and bufio.Writer and prevents them from being garbage collected. To fix the problem this change removes persistConn and error references from wantConn and delivers them via channel to getConn. This way wantConn could be kept in wantConnQueues arbitrary long. Fixes golang#43966 Fixes golang#50798
1 parent 4711299 commit 9e95e4b

File tree

1 file changed

+62
-63
lines changed

1 file changed

+62
-63
lines changed
 

Diff for: ‎src/net/http/transport.go

+62-63
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
953953
// Loop over the waiting list until we find a w that isn't done already, and hand it pconn.
954954
for q.len() > 0 {
955955
w := q.popFront()
956-
if w.tryDeliver(pconn, nil) {
956+
if w.tryDeliver(pconn, nil, time.Time{}) {
957957
done = true
958958
break
959959
}
@@ -965,7 +965,7 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
965965
// list unconditionally, for any future clients too.
966966
for q.len() > 0 {
967967
w := q.popFront()
968-
w.tryDeliver(pconn, nil)
968+
w.tryDeliver(pconn, nil, time.Time{})
969969
}
970970
}
971971
if q.len() == 0 {
@@ -1069,7 +1069,7 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
10691069
list = list[:len(list)-1]
10701070
continue
10711071
}
1072-
delivered = w.tryDeliver(pconn, nil)
1072+
delivered = w.tryDeliver(pconn, nil, pconn.idleAt)
10731073
if delivered {
10741074
if pconn.alt != nil {
10751075
// HTTP/2: multiple clients can share pconn.
@@ -1203,69 +1203,77 @@ func (t *Transport) dial(ctx context.Context, network, addr string) (net.Conn, e
12031203
// These three options are racing against each other and use
12041204
// wantConn to coordinate and agree about the winning outcome.
12051205
type wantConn struct {
1206-
cm connectMethod
1207-
key connectMethodKey // cm.key()
1208-
ready chan struct{} // closed when pc, err pair is delivered
1206+
cm connectMethod
1207+
key connectMethodKey // cm.key()
12091208

12101209
// hooks for testing to know when dials are done
12111210
// beforeDial is called in the getConn goroutine when the dial is queued.
12121211
// afterDial is called when the dial is completed or canceled.
12131212
beforeDial func()
12141213
afterDial func()
12151214

1216-
mu sync.Mutex // protects ctx, pc, err, close(ready)
1217-
ctx context.Context // context for dial, cleared after delivered or canceled
1218-
pc *persistConn
1219-
err error
1215+
mu sync.Mutex // protects ctx, done and sending of the result
1216+
ctx context.Context // context for dial, cleared after delivered or canceled
1217+
done bool // true after delivered or canceled
1218+
result chan connOrError // channel to deliver connection or error
1219+
}
1220+
1221+
type connOrError struct {
1222+
pc *persistConn
1223+
err error
1224+
idleAt time.Time
12201225
}
12211226

12221227
// waiting reports whether w is still waiting for an answer (connection or error).
12231228
func (w *wantConn) waiting() bool {
1224-
select {
1225-
case <-w.ready:
1226-
return false
1227-
default:
1228-
return true
1229-
}
1229+
w.mu.Lock()
1230+
defer w.mu.Unlock()
1231+
1232+
return !w.done
12301233
}
12311234

12321235
// getCtxForDial returns context for dial or nil if connection was delivered or canceled.
12331236
func (w *wantConn) getCtxForDial() context.Context {
12341237
w.mu.Lock()
12351238
defer w.mu.Unlock()
1239+
12361240
return w.ctx
12371241
}
12381242

12391243
// tryDeliver attempts to deliver pc, err to w and reports whether it succeeded.
1240-
func (w *wantConn) tryDeliver(pc *persistConn, err error) bool {
1244+
func (w *wantConn) tryDeliver(pc *persistConn, err error, idleAt time.Time) bool {
12411245
w.mu.Lock()
12421246
defer w.mu.Unlock()
12431247

1244-
if w.pc != nil || w.err != nil {
1248+
if w.done {
12451249
return false
12461250
}
1247-
1248-
w.ctx = nil
1249-
w.pc = pc
1250-
w.err = err
1251-
if w.pc == nil && w.err == nil {
1251+
if (pc == nil) == (err == nil) {
12521252
panic("net/http: internal error: misuse of tryDeliver")
12531253
}
1254-
close(w.ready)
1254+
w.ctx = nil
1255+
w.done = true
1256+
1257+
w.result <- connOrError{pc: pc, err: err, idleAt: idleAt}
1258+
close(w.result)
1259+
12551260
return true
12561261
}
12571262

12581263
// cancel marks w as no longer wanting a result (for example, due to cancellation).
12591264
// If a connection has been delivered already, cancel returns it with t.putOrCloseIdleConn.
12601265
func (w *wantConn) cancel(t *Transport, err error) {
12611266
w.mu.Lock()
1262-
if w.pc == nil && w.err == nil {
1263-
close(w.ready) // catch misbehavior in future delivery
1267+
var pc *persistConn
1268+
if w.done {
1269+
if r, ok := <-w.result; ok {
1270+
pc = r.pc
1271+
}
1272+
} else {
1273+
close(w.result)
12641274
}
1265-
pc := w.pc
12661275
w.ctx = nil
1267-
w.pc = nil
1268-
w.err = err
1276+
w.done = true
12691277
w.mu.Unlock()
12701278

12711279
if pc != nil {
@@ -1355,7 +1363,7 @@ func (t *Transport) customDialTLS(ctx context.Context, network, addr string) (co
13551363
// specified in the connectMethod. This includes doing a proxy CONNECT
13561364
// and/or setting up TLS. If this doesn't return an error, the persistConn
13571365
// is ready to write requests to.
1358-
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
1366+
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (_ *persistConn, err error) {
13591367
req := treq.Request
13601368
trace := treq.trace
13611369
ctx := req.Context()
@@ -1367,7 +1375,7 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
13671375
cm: cm,
13681376
key: cm.key(),
13691377
ctx: ctx,
1370-
ready: make(chan struct{}, 1),
1378+
result: make(chan connOrError, 1),
13711379
beforeDial: testHookPrePendingDial,
13721380
afterDial: testHookPostPendingDial,
13731381
}
@@ -1377,38 +1385,41 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
13771385
}
13781386
}()
13791387

1388+
var cancelc chan error
1389+
13801390
// Queue for idle connection.
13811391
if delivered := t.queueForIdleConn(w); delivered {
1382-
pc := w.pc
1383-
// Trace only for HTTP/1.
1384-
// HTTP/2 calls trace.GotConn itself.
1385-
if pc.alt == nil && trace != nil && trace.GotConn != nil {
1386-
trace.GotConn(pc.gotIdleConnTrace(pc.idleAt))
1387-
}
13881392
// set request canceler to some non-nil function so we
13891393
// can detect whether it was cleared between now and when
13901394
// we enter roundTrip
13911395
t.setReqCanceler(treq.cancelKey, func(error) {})
1392-
return pc, nil
1393-
}
1394-
1395-
cancelc := make(chan error, 1)
1396-
t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })
1396+
} else {
1397+
cancelc = make(chan error, 1)
1398+
t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })
13971399

1398-
// Queue for permission to dial.
1399-
t.queueForDial(w)
1400+
// Queue for permission to dial.
1401+
t.queueForDial(w)
1402+
}
14001403

14011404
// Wait for completion or cancellation.
14021405
select {
1403-
case <-w.ready:
1406+
case r := <-w.result:
14041407
// Trace success but only for HTTP/1.
14051408
// HTTP/2 calls trace.GotConn itself.
1406-
if w.pc != nil && w.pc.alt == nil && trace != nil && trace.GotConn != nil {
1407-
trace.GotConn(httptrace.GotConnInfo{Conn: w.pc.conn, Reused: w.pc.isReused()})
1409+
if r.pc != nil && r.pc.alt == nil && trace != nil && trace.GotConn != nil {
1410+
info := httptrace.GotConnInfo{
1411+
Conn: r.pc.conn,
1412+
Reused: r.pc.isReused(),
1413+
}
1414+
if !r.idleAt.IsZero() {
1415+
info.WasIdle = true
1416+
info.IdleTime = time.Since(r.idleAt)
1417+
}
1418+
trace.GotConn(info)
14081419
}
1409-
if w.err != nil {
1420+
if r.err != nil {
14101421
// If the request has been canceled, that's probably
1411-
// what caused w.err; if so, prefer to return the
1422+
// what caused r.err; if so, prefer to return the
14121423
// cancellation error (see golang.org/issue/16049).
14131424
select {
14141425
case <-req.Cancel:
@@ -1424,7 +1435,7 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
14241435
// return below
14251436
}
14261437
}
1427-
return w.pc, w.err
1438+
return r.pc, r.err
14281439
case <-req.Cancel:
14291440
return nil, errRequestCanceledConn
14301441
case <-req.Context().Done():
@@ -1478,7 +1489,7 @@ func (t *Transport) dialConnFor(w *wantConn) {
14781489
}
14791490

14801491
pc, err := t.dialConn(ctx, w.cm)
1481-
delivered := w.tryDeliver(pc, err)
1492+
delivered := w.tryDeliver(pc, err, time.Time{})
14821493
if err == nil && (!delivered || pc.alt != nil) {
14831494
// pconn was not passed to w,
14841495
// or it is HTTP/2 and can be shared.
@@ -1996,18 +2007,6 @@ func (pc *persistConn) isReused() bool {
19962007
return r
19972008
}
19982009

1999-
func (pc *persistConn) gotIdleConnTrace(idleAt time.Time) (t httptrace.GotConnInfo) {
2000-
pc.mu.Lock()
2001-
defer pc.mu.Unlock()
2002-
t.Reused = pc.reused
2003-
t.Conn = pc.conn
2004-
t.WasIdle = true
2005-
if !idleAt.IsZero() {
2006-
t.IdleTime = time.Since(idleAt)
2007-
}
2008-
return
2009-
}
2010-
20112010
func (pc *persistConn) cancelRequest(err error) {
20122011
pc.mu.Lock()
20132012
defer pc.mu.Unlock()

0 commit comments

Comments
 (0)
Please sign in to comment.