Commit a333c534 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

http2: add Transport support for IdleConnTimeout

Tests will be in the go repo's net/http package when this
package is re-bundled into std.

Updates golang/go#16808 (fixes after bundle into std)

Change-Id: Iad31dc120bc008b1e9679bf7b2b988aac9c893c8
Reviewed-on: https://go-review.googlesource.com/30075
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent 8058fc7b
...@@ -39,6 +39,13 @@ type clientTrace httptrace.ClientTrace ...@@ -39,6 +39,13 @@ type clientTrace httptrace.ClientTrace
func reqContext(r *http.Request) context.Context { return r.Context() } func reqContext(r *http.Request) context.Context { return r.Context() }
func (t *Transport) idleConnTimeout() time.Duration {
if t.t1 != nil {
return t.t1.IdleConnTimeout
}
return 0
}
func setResponseUncompressed(res *http.Response) { res.Uncompressed = true } func setResponseUncompressed(res *http.Response) { res.Uncompressed = true }
func traceGotConn(req *http.Request, cc *ClientConn) { func traceGotConn(req *http.Request, cc *ClientConn) {
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"crypto/tls" "crypto/tls"
"net" "net"
"net/http" "net/http"
"time"
) )
type contextContext interface { type contextContext interface {
...@@ -82,3 +83,5 @@ func cloneTLSConfig(c *tls.Config) *tls.Config { ...@@ -82,3 +83,5 @@ func cloneTLSConfig(c *tls.Config) *tls.Config {
func (cc *ClientConn) Ping(ctx contextContext) error { func (cc *ClientConn) Ping(ctx contextContext) error {
return cc.ping(ctx) return cc.ping(ctx)
} }
func (t *Transport) idleConnTimeout() time.Duration { return 0 }
...@@ -151,6 +151,9 @@ type ClientConn struct { ...@@ -151,6 +151,9 @@ type ClientConn struct {
readerDone chan struct{} // closed on error readerDone chan struct{} // closed on error
readerErr error // set before readerDone is closed readerErr error // set before readerDone is closed
idleTimeout time.Duration // or 0 for never
idleTimer *time.Timer
mu sync.Mutex // guards following mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes cond *sync.Cond // hold mu; broadcast on flow/closed changes
flow flow // our conn-level flow control quota (cs.flow is per stream) flow flow // our conn-level flow control quota (cs.flow is per stream)
...@@ -435,6 +438,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro ...@@ -435,6 +438,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
wantSettingsAck: true, wantSettingsAck: true,
pings: make(map[[8]byte]chan struct{}), pings: make(map[[8]byte]chan struct{}),
} }
if d := t.idleConnTimeout(); d != 0 {
cc.idleTimeout = d
cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
}
if VerboseLogs { if VerboseLogs {
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr()) t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
} }
...@@ -511,6 +518,16 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool { ...@@ -511,6 +518,16 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
cc.nextStreamID < math.MaxInt32 cc.nextStreamID < math.MaxInt32
} }
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
// only be called when we're idle, but because we're coming from a new
// goroutine, there could be a new request coming in at the same time,
// so this simply calls the synchronized closeIfIdle to shut down this
// connection. The timer could just call closeIfIdle, but this is more
// clear.
func (cc *ClientConn) onIdleTimeout() {
cc.closeIfIdle()
}
func (cc *ClientConn) closeIfIdle() { func (cc *ClientConn) closeIfIdle() {
cc.mu.Lock() cc.mu.Lock()
if len(cc.streams) > 0 { if len(cc.streams) > 0 {
...@@ -652,6 +669,9 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { ...@@ -652,6 +669,9 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
if err := checkConnHeaders(req); err != nil { if err := checkConnHeaders(req); err != nil {
return nil, err return nil, err
} }
if cc.idleTimer != nil {
cc.idleTimer.Stop()
}
trailers, err := commaSeparatedTrailers(req) trailers, err := commaSeparatedTrailers(req)
if err != nil { if err != nil {
...@@ -1176,6 +1196,9 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream { ...@@ -1176,6 +1196,9 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
if andRemove && cs != nil && !cc.closed { if andRemove && cs != nil && !cc.closed {
cc.lastActive = time.Now() cc.lastActive = time.Now()
delete(cc.streams, id) delete(cc.streams, id)
if len(cc.streams) == 0 && cc.idleTimer != nil {
cc.idleTimer.Reset(cc.idleTimeout)
}
close(cs.done) close(cs.done)
cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl cc.cond.Broadcast() // wake up checkResetOrDone via clientStream.awaitFlowControl
} }
...@@ -1232,6 +1255,10 @@ func (rl *clientConnReadLoop) cleanup() { ...@@ -1232,6 +1255,10 @@ func (rl *clientConnReadLoop) cleanup() {
defer cc.t.connPool().MarkDead(cc) defer cc.t.connPool().MarkDead(cc)
defer close(cc.readerDone) defer close(cc.readerDone)
if cc.idleTimer != nil {
cc.idleTimer.Stop()
}
// Close any response bodies if the server closes prematurely. // Close any response bodies if the server closes prematurely.
// TODO: also do this if we've written the headers but not // TODO: also do this if we've written the headers but not
// gotten a response yet. // gotten a response yet.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment