|
@@ -49,6 +49,10 @@ type Stream struct {
|
|
|
|
|
|
readDeadline atomic.Value // time.Time
|
|
readDeadline atomic.Value // time.Time
|
|
writeDeadline atomic.Value // time.Time
|
|
writeDeadline atomic.Value // time.Time
|
|
|
|
+
|
|
|
|
+ // closeTimer is set with stateLock held to honor the StreamCloseTimeout
|
|
|
|
+ // setting on Session.
|
|
|
|
+ closeTimer *time.Timer
|
|
}
|
|
}
|
|
|
|
|
|
// newStream is used to construct a new stream within
|
|
// newStream is used to construct a new stream within
|
|
@@ -312,6 +316,27 @@ func (s *Stream) Close() error {
|
|
s.stateLock.Unlock()
|
|
s.stateLock.Unlock()
|
|
return nil
|
|
return nil
|
|
SEND_CLOSE:
|
|
SEND_CLOSE:
|
|
|
|
+ // This shouldn't happen (the more realistic scenario to cancel the
|
|
|
|
+ // timer is via processFlags) but just in case this ever happens, we
|
|
|
|
+ // cancel the timer to prevent dangling timers.
|
|
|
|
+ if s.closeTimer != nil {
|
|
|
|
+ s.closeTimer.Stop()
|
|
|
|
+ s.closeTimer = nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If we have a StreamCloseTimeout set we start the timeout timer.
|
|
|
|
+ // We do this only if we're not already closing the stream since that
|
|
|
|
+ // means this was a graceful close.
|
|
|
|
+ //
|
|
|
|
+ // This prevents memory leaks if one side (this side) closes and the
|
|
|
|
+ // remote side poorly behaves and never responds with a FIN to complete
|
|
|
|
+ // the close. After the specified timeout, we clean our resources up no
|
|
|
|
+ // matter what.
|
|
|
|
+ if !closeStream && s.session.config.StreamCloseTimeout > 0 {
|
|
|
|
+ s.closeTimer = time.AfterFunc(
|
|
|
|
+ s.session.config.StreamCloseTimeout, s.closeTimeout)
|
|
|
|
+ }
|
|
|
|
+
|
|
s.stateLock.Unlock()
|
|
s.stateLock.Unlock()
|
|
s.sendClose()
|
|
s.sendClose()
|
|
s.notifyWaiting()
|
|
s.notifyWaiting()
|
|
@@ -321,6 +346,22 @@ SEND_CLOSE:
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// closeTimeout is called after StreamCloseTimeout during a close to
|
|
|
|
+// close this stream.
|
|
|
|
+func (s *Stream) closeTimeout() {
|
|
|
|
+ // Close our side forcibly
|
|
|
|
+ s.forceClose()
|
|
|
|
+
|
|
|
|
+ // Free the stream from the session map
|
|
|
|
+ s.session.closeStream(s.id)
|
|
|
|
+
|
|
|
|
+ // Send a RST so the remote side closes too.
|
|
|
|
+ s.sendLock.Lock()
|
|
|
|
+ defer s.sendLock.Unlock()
|
|
|
|
+ s.sendHdr.encode(typeWindowUpdate, flagRST, s.id, 0)
|
|
|
|
+ s.session.sendNoWait(s.sendHdr)
|
|
|
|
+}
|
|
|
|
+
|
|
// forceClose is used for when the session is exiting
|
|
// forceClose is used for when the session is exiting
|
|
func (s *Stream) forceClose() {
|
|
func (s *Stream) forceClose() {
|
|
s.stateLock.Lock()
|
|
s.stateLock.Lock()
|
|
@@ -332,16 +373,22 @@ func (s *Stream) forceClose() {
|
|
// processFlags is used to update the state of the stream
|
|
// processFlags is used to update the state of the stream
|
|
// based on set flags, if any. Lock must be held
|
|
// based on set flags, if any. Lock must be held
|
|
func (s *Stream) processFlags(flags uint16) error {
|
|
func (s *Stream) processFlags(flags uint16) error {
|
|
|
|
+ s.stateLock.Lock()
|
|
|
|
+ defer s.stateLock.Unlock()
|
|
|
|
+
|
|
// Close the stream without holding the state lock
|
|
// Close the stream without holding the state lock
|
|
closeStream := false
|
|
closeStream := false
|
|
defer func() {
|
|
defer func() {
|
|
if closeStream {
|
|
if closeStream {
|
|
|
|
+ if s.closeTimer != nil {
|
|
|
|
+ // Stop our close timeout timer since we gracefully closed
|
|
|
|
+ s.closeTimer.Stop()
|
|
|
|
+ }
|
|
|
|
+
|
|
s.session.closeStream(s.id)
|
|
s.session.closeStream(s.id)
|
|
}
|
|
}
|
|
}()
|
|
}()
|
|
|
|
|
|
- s.stateLock.Lock()
|
|
|
|
- defer s.stateLock.Unlock()
|
|
|
|
if flags&flagACK == flagACK {
|
|
if flags&flagACK == flagACK {
|
|
if s.state == streamSYNSent {
|
|
if s.state == streamSYNSent {
|
|
s.state = streamEstablished
|
|
s.state = streamEstablished
|