Skip to content

Commit e9d95ba

Browse files
committed
http2: do not surface errors from a conn's idle timer expiring
CL 625398 surfaces errors occurring on a client conn before it has been used for any requests. Don't surface "errors" arising from a conn being closed for idleness without ever being used. For golang/go#70515 Change-Id: I2b45215f90f74fee66ee46f3b62d27117147c64a Reviewed-on: https://go-review.googlesource.com/c/net/+/631815 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: David Chase <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent c2be992 commit e9d95ba

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

http2/transport.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ type ClientConn struct {
375375
doNotReuse bool // whether conn is marked to not be reused for any future requests
376376
closing bool
377377
closed bool
378+
closedOnIdle bool // true if conn was closed for idleness
378379
seenSettings bool // true if we've seen a settings frame, false otherwise
379380
seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails
380381
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
@@ -1089,10 +1090,12 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
10891090

10901091
// If this connection has never been used for a request and is closed,
10911092
// then let it take a request (which will fail).
1093+
// If the conn was closed for idleness, we're racing the idle timer;
1094+
// don't try to use the conn. (Issue #70515.)
10921095
//
10931096
// This avoids a situation where an error early in a connection's lifetime
10941097
// goes unreported.
1095-
if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed {
1098+
if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed && !cc.closedOnIdle {
10961099
st.canTakeNewRequest = true
10971100
}
10981101

@@ -1155,6 +1158,7 @@ func (cc *ClientConn) closeIfIdle() {
11551158
return
11561159
}
11571160
cc.closed = true
1161+
cc.closedOnIdle = true
11581162
nextID := cc.nextStreamID
11591163
// TODO: do clients send GOAWAY too? maybe? Just Close:
11601164
cc.mu.Unlock()
@@ -2434,9 +2438,12 @@ func (rl *clientConnReadLoop) cleanup() {
24342438
// This avoids a situation where new connections are constantly created,
24352439
// added to the pool, fail, and are removed from the pool, without any error
24362440
// being surfaced to the user.
2437-
const unusedWaitTime = 5 * time.Second
2441+
unusedWaitTime := 5 * time.Second
2442+
if cc.idleTimeout > 0 && unusedWaitTime > cc.idleTimeout {
2443+
unusedWaitTime = cc.idleTimeout
2444+
}
24382445
idleTime := cc.t.now().Sub(cc.lastActive)
2439-
if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime {
2446+
if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime && !cc.closedOnIdle {
24402447
cc.idleTimer = cc.t.afterFunc(unusedWaitTime-idleTime, func() {
24412448
cc.t.connPool().MarkDead(cc)
24422449
})

http2/transport_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -5789,6 +5789,38 @@ func TestTransportTLSNextProtoConnImmediateFailureUsed(t *testing.T) {
57895789
}
57905790
}
57915791

5792+
// Test the case where a conn provided via a TLSNextProto hook is closed for idleness
5793+
// before we use it.
5794+
func TestTransportTLSNextProtoConnIdleTimoutBeforeUse(t *testing.T) {
5795+
t1 := &http.Transport{
5796+
IdleConnTimeout: 1 * time.Second,
5797+
}
5798+
t2, _ := ConfigureTransports(t1)
5799+
tt := newTestTransport(t, t2)
5800+
5801+
// Create a new, fake connection and pass it to the Transport via the TLSNextProto hook.
5802+
cli, _ := synctestNetPipe(tt.group)
5803+
cliTLS := tls.Client(cli, tlsConfigInsecure)
5804+
go func() {
5805+
tt.group.Join()
5806+
t1.TLSNextProto["h2"]("dummy.tld", cliTLS)
5807+
}()
5808+
tt.sync()
5809+
tc := tt.getConn()
5810+
5811+
// The connection encounters an error before we send a request that uses it.
5812+
tc.advance(2 * time.Second)
5813+
5814+
// Send a request on the Transport.
5815+
//
5816+
// It should fail with ErrNoCachedConn.
5817+
req := must(http.NewRequest("GET", "https://dummy.tld/", nil))
5818+
rt := tt.roundTrip(req)
5819+
if err := rt.err(); !errors.Is(err, ErrNoCachedConn) {
5820+
t.Fatalf("RoundTrip with conn closed for idleness: got %v, want ErrNoCachedConn", err)
5821+
}
5822+
}
5823+
57925824
// Test the case where a conn provided via a TLSNextProto hook immediately encounters an error,
57935825
// but no requests are sent which would use the bad connection.
57945826
func TestTransportTLSNextProtoConnImmediateFailureUnused(t *testing.T) {

0 commit comments

Comments
 (0)