|
@@ -227,6 +227,7 @@ type ClientConn struct {
|
|
|
br *bufio.Reader
|
|
br *bufio.Reader
|
|
|
fr *Framer
|
|
fr *Framer
|
|
|
lastActive time.Time
|
|
lastActive time.Time
|
|
|
|
|
+ lastIdle time.Time // time last idle
|
|
|
// Settings from peer: (also guarded by mu)
|
|
// Settings from peer: (also guarded by mu)
|
|
|
maxFrameSize uint32
|
|
maxFrameSize uint32
|
|
|
maxConcurrentStreams uint32
|
|
maxConcurrentStreams uint32
|
|
@@ -736,7 +737,8 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
|
|
st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
|
|
|
- int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32
|
|
|
|
|
|
|
+ int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
|
|
|
|
|
+ !cc.tooIdleLocked()
|
|
|
st.freshConn = cc.nextStreamID == 1 && st.canTakeNewRequest
|
|
st.freshConn = cc.nextStreamID == 1 && st.canTakeNewRequest
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -746,6 +748,16 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
|
|
|
return st.canTakeNewRequest
|
|
return st.canTakeNewRequest
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// tooIdleLocked reports whether this connection has been been sitting idle
|
|
|
|
|
+// for too much wall time.
|
|
|
|
|
+func (cc *ClientConn) tooIdleLocked() bool {
|
|
|
|
|
+ // The Round(0) strips the monontonic clock reading so the
|
|
|
|
|
+ // times are compared based on their wall time. We don't want
|
|
|
|
|
+ // to reuse a connection that's been sitting idle during
|
|
|
|
|
+ // VM/laptop suspend if monotonic time was also frozen.
|
|
|
|
|
+ return cc.idleTimeout != 0 && !cc.lastIdle.IsZero() && time.Since(cc.lastIdle.Round(0)) > cc.idleTimeout
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
|
|
// 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
|
|
// 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,
|
|
// goroutine, there could be a new request coming in at the same time,
|
|
@@ -1150,6 +1162,7 @@ func (cc *ClientConn) awaitOpenSlotForRequest(req *http.Request) error {
|
|
|
}
|
|
}
|
|
|
return errClientConnUnusable
|
|
return errClientConnUnusable
|
|
|
}
|
|
}
|
|
|
|
|
+ cc.lastIdle = time.Time{}
|
|
|
if int64(len(cc.streams))+1 <= int64(cc.maxConcurrentStreams) {
|
|
if int64(len(cc.streams))+1 <= int64(cc.maxConcurrentStreams) {
|
|
|
if waitingForConn != nil {
|
|
if waitingForConn != nil {
|
|
|
close(waitingForConn)
|
|
close(waitingForConn)
|
|
@@ -1638,6 +1651,7 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
|
|
|
delete(cc.streams, id)
|
|
delete(cc.streams, id)
|
|
|
if len(cc.streams) == 0 && cc.idleTimer != nil {
|
|
if len(cc.streams) == 0 && cc.idleTimer != nil {
|
|
|
cc.idleTimer.Reset(cc.idleTimeout)
|
|
cc.idleTimer.Reset(cc.idleTimeout)
|
|
|
|
|
+ cc.lastIdle = time.Now()
|
|
|
}
|
|
}
|
|
|
close(cs.done)
|
|
close(cs.done)
|
|
|
// Wake up checkResetOrDone via clientStream.awaitFlowControl and
|
|
// Wake up checkResetOrDone via clientStream.awaitFlowControl and
|