Skip to content

Commit 027a083

Browse files
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 #43966 Fixes #50798
1 parent 58052fe commit 027a083

File tree

1 file changed

+62
-63
lines changed

1 file changed

+62
-63
lines changed

src/net/http/transport.go

+62-63
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
957957
// Loop over the waiting list until we find a w that isn't done already, and hand it pconn.
958958
for q.len() > 0 {
959959
w := q.popFront()
960-
if w.tryDeliver(pconn, nil) {
960+
if w.tryDeliver(pconn, nil, time.Time{}) {
961961
done = true
962962
break
963963
}
@@ -969,7 +969,7 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
969969
// list unconditionally, for any future clients too.
970970
for q.len() > 0 {
971971
w := q.popFront()
972-
w.tryDeliver(pconn, nil)
972+
w.tryDeliver(pconn, nil, time.Time{})
973973
}
974974
}
975975
if q.len() == 0 {
@@ -1073,7 +1073,7 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
10731073
list = list[:len(list)-1]
10741074
continue
10751075
}
1076-
delivered = w.tryDeliver(pconn, nil)
1076+
delivered = w.tryDeliver(pconn, nil, pconn.idleAt)
10771077
if delivered {
10781078
if pconn.alt != nil {
10791079
// HTTP/2: multiple clients can share pconn.
@@ -1207,69 +1207,77 @@ func (t *Transport) dial(ctx context.Context, network, addr string) (net.Conn, e
12071207
// These three options are racing against each other and use
12081208
// wantConn to coordinate and agree about the winning outcome.
12091209
type wantConn struct {
1210-
cm connectMethod
1211-
key connectMethodKey // cm.key()
1212-
ready chan struct{} // closed when pc, err pair is delivered
1210+
cm connectMethod
1211+
key connectMethodKey // cm.key()
12131212

12141213
// hooks for testing to know when dials are done
12151214
// beforeDial is called in the getConn goroutine when the dial is queued.
12161215
// afterDial is called when the dial is completed or canceled.
12171216
beforeDial func()
12181217
afterDial func()
12191218

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

12261231
// waiting reports whether w is still waiting for an answer (connection or error).
12271232
func (w *wantConn) waiting() bool {
1228-
select {
1229-
case <-w.ready:
1230-
return false
1231-
default:
1232-
return true
1233-
}
1233+
w.mu.Lock()
1234+
defer w.mu.Unlock()
1235+
1236+
return !w.done
12341237
}
12351238

12361239
// getCtxForDial returns context for dial or nil if connection was delivered or canceled.
12371240
func (w *wantConn) getCtxForDial() context.Context {
12381241
w.mu.Lock()
12391242
defer w.mu.Unlock()
1243+
12401244
return w.ctx
12411245
}
12421246

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

1248-
if w.pc != nil || w.err != nil {
1252+
if w.done {
12491253
return false
12501254
}
1251-
1252-
w.ctx = nil
1253-
w.pc = pc
1254-
w.err = err
1255-
if w.pc == nil && w.err == nil {
1255+
if (pc == nil) == (err == nil) {
12561256
panic("net/http: internal error: misuse of tryDeliver")
12571257
}
1258-
close(w.ready)
1258+
w.ctx = nil
1259+
w.done = true
1260+
1261+
w.result <- connOrError{pc: pc, err: err, idleAt: idleAt}
1262+
close(w.result)
1263+
12591264
return true
12601265
}
12611266

12621267
// cancel marks w as no longer wanting a result (for example, due to cancellation).
12631268
// If a connection has been delivered already, cancel returns it with t.putOrCloseIdleConn.
12641269
func (w *wantConn) cancel(t *Transport, err error) {
12651270
w.mu.Lock()
1266-
if w.pc == nil && w.err == nil {
1267-
close(w.ready) // catch misbehavior in future delivery
1271+
var pc *persistConn
1272+
if w.done {
1273+
if r, ok := <-w.result; ok {
1274+
pc = r.pc
1275+
}
1276+
} else {
1277+
close(w.result)
12681278
}
1269-
pc := w.pc
12701279
w.ctx = nil
1271-
w.pc = nil
1272-
w.err = err
1280+
w.done = true
12731281
w.mu.Unlock()
12741282

12751283
if pc != nil {
@@ -1359,7 +1367,7 @@ func (t *Transport) customDialTLS(ctx context.Context, network, addr string) (co
13591367
// specified in the connectMethod. This includes doing a proxy CONNECT
13601368
// and/or setting up TLS. If this doesn't return an error, the persistConn
13611369
// is ready to write requests to.
1362-
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
1370+
func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (_ *persistConn, err error) {
13631371
req := treq.Request
13641372
trace := treq.trace
13651373
ctx := req.Context()
@@ -1371,7 +1379,7 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
13711379
cm: cm,
13721380
key: cm.key(),
13731381
ctx: ctx,
1374-
ready: make(chan struct{}, 1),
1382+
result: make(chan connOrError, 1),
13751383
beforeDial: testHookPrePendingDial,
13761384
afterDial: testHookPostPendingDial,
13771385
}
@@ -1381,38 +1389,41 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
13811389
}
13821390
}()
13831391

1392+
var cancelc chan error
1393+
13841394
// Queue for idle connection.
13851395
if delivered := t.queueForIdleConn(w); delivered {
1386-
pc := w.pc
1387-
// Trace only for HTTP/1.
1388-
// HTTP/2 calls trace.GotConn itself.
1389-
if pc.alt == nil && trace != nil && trace.GotConn != nil {
1390-
trace.GotConn(pc.gotIdleConnTrace(pc.idleAt))
1391-
}
13921396
// set request canceler to some non-nil function so we
13931397
// can detect whether it was cleared between now and when
13941398
// we enter roundTrip
13951399
t.setReqCanceler(treq.cancelKey, func(error) {})
1396-
return pc, nil
1397-
}
1398-
1399-
cancelc := make(chan error, 1)
1400-
t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })
1400+
} else {
1401+
cancelc = make(chan error, 1)
1402+
t.setReqCanceler(treq.cancelKey, func(err error) { cancelc <- err })
14011403

1402-
// Queue for permission to dial.
1403-
t.queueForDial(w)
1404+
// Queue for permission to dial.
1405+
t.queueForDial(w)
1406+
}
14041407

14051408
// Wait for completion or cancellation.
14061409
select {
1407-
case <-w.ready:
1410+
case r := <-w.result:
14081411
// Trace success but only for HTTP/1.
14091412
// HTTP/2 calls trace.GotConn itself.
1410-
if w.pc != nil && w.pc.alt == nil && trace != nil && trace.GotConn != nil {
1411-
trace.GotConn(httptrace.GotConnInfo{Conn: w.pc.conn, Reused: w.pc.isReused()})
1413+
if r.pc != nil && r.pc.alt == nil && trace != nil && trace.GotConn != nil {
1414+
info := httptrace.GotConnInfo{
1415+
Conn: r.pc.conn,
1416+
Reused: r.pc.isReused(),
1417+
}
1418+
if !r.idleAt.IsZero() {
1419+
info.WasIdle = true
1420+
info.IdleTime = time.Since(r.idleAt)
1421+
}
1422+
trace.GotConn(info)
14121423
}
1413-
if w.err != nil {
1424+
if r.err != nil {
14141425
// If the request has been canceled, that's probably
1415-
// what caused w.err; if so, prefer to return the
1426+
// what caused r.err; if so, prefer to return the
14161427
// cancellation error (see golang.org/issue/16049).
14171428
select {
14181429
case <-req.Cancel:
@@ -1428,7 +1439,7 @@ func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persi
14281439
// return below
14291440
}
14301441
}
1431-
return w.pc, w.err
1442+
return r.pc, r.err
14321443
case <-req.Cancel:
14331444
return nil, errRequestCanceledConn
14341445
case <-req.Context().Done():
@@ -1483,7 +1494,7 @@ func (t *Transport) dialConnFor(w *wantConn) {
14831494
}
14841495

14851496
pc, err := t.dialConn(ctx, w.cm)
1486-
delivered := w.tryDeliver(pc, err)
1497+
delivered := w.tryDeliver(pc, err, time.Time{})
14871498
if err == nil && (!delivered || pc.alt != nil) {
14881499
// pconn was not passed to w,
14891500
// or it is HTTP/2 and can be shared.
@@ -2007,18 +2018,6 @@ func (pc *persistConn) isReused() bool {
20072018
return r
20082019
}
20092020

2010-
func (pc *persistConn) gotIdleConnTrace(idleAt time.Time) (t httptrace.GotConnInfo) {
2011-
pc.mu.Lock()
2012-
defer pc.mu.Unlock()
2013-
t.Reused = pc.reused
2014-
t.Conn = pc.conn
2015-
t.WasIdle = true
2016-
if !idleAt.IsZero() {
2017-
t.IdleTime = time.Since(idleAt)
2018-
}
2019-
return
2020-
}
2021-
20222021
func (pc *persistConn) cancelRequest(err error) {
20232022
pc.mu.Lock()
20242023
defer pc.mu.Unlock()

0 commit comments

Comments
 (0)