all: Send Close BEP msg on intentional disconnect (#5440)
This avoids waiting until next ping and timeout until the connection is actually closed both by notifying the peer of the disconnect and by immediately closing the local end of the connection after that. As a nice side effect, info level logging about dropped connections now have the actual reason in it, not a generic timeout error which looks like a real problem with the connection.
This commit is contained in:
@@ -143,6 +143,7 @@ type RequestResponse interface {
|
||||
|
||||
type Connection interface {
|
||||
Start()
|
||||
Close(err error)
|
||||
ID() DeviceID
|
||||
Name() string
|
||||
Index(folder string, files []FileInfo) error
|
||||
@@ -171,6 +172,7 @@ type rawConnection struct {
|
||||
nextIDMut sync.Mutex
|
||||
|
||||
outbox chan asyncMessage
|
||||
sendClose chan asyncMessage
|
||||
closed chan struct{}
|
||||
once sync.Once
|
||||
compression Compression
|
||||
@@ -214,6 +216,7 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
|
||||
cw: cw,
|
||||
awaiting: make(map[int32]chan asyncResult),
|
||||
outbox: make(chan asyncMessage),
|
||||
sendClose: make(chan asyncMessage),
|
||||
closed: make(chan struct{}),
|
||||
compression: compress,
|
||||
}
|
||||
@@ -334,7 +337,7 @@ func (c *rawConnection) ping() bool {
|
||||
|
||||
func (c *rawConnection) readerLoop() (err error) {
|
||||
defer func() {
|
||||
c.close(err)
|
||||
c.internalClose(err)
|
||||
}()
|
||||
|
||||
fourByteBuf := make([]byte, 4)
|
||||
@@ -636,10 +639,15 @@ func (c *rawConnection) writerLoop() {
|
||||
close(hm.done)
|
||||
}
|
||||
if err != nil {
|
||||
c.close(err)
|
||||
c.internalClose(err)
|
||||
return
|
||||
}
|
||||
|
||||
case m := <-c.sendClose:
|
||||
c.writeMessage(m)
|
||||
close(m.done)
|
||||
return // No message must be sent after the Close message.
|
||||
|
||||
case <-c.closed:
|
||||
return
|
||||
}
|
||||
@@ -801,24 +809,47 @@ func (c *rawConnection) shouldCompressMessage(msg message) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rawConnection) close(err error) {
|
||||
// Close is called when the connection is regularely closed and thus the Close
|
||||
// BEP message is sent before terminating the actual connection. The error
|
||||
// argument specifies the reason for closing the connection.
|
||||
func (c *rawConnection) Close(err error) {
|
||||
c.once.Do(func() {
|
||||
l.Debugln("close due to", err)
|
||||
close(c.closed)
|
||||
done := make(chan struct{})
|
||||
c.sendClose <- asyncMessage{&Close{err.Error()}, done}
|
||||
<-done
|
||||
|
||||
c.awaitingMut.Lock()
|
||||
for i, ch := range c.awaiting {
|
||||
if ch != nil {
|
||||
close(ch)
|
||||
delete(c.awaiting, i)
|
||||
}
|
||||
}
|
||||
c.awaitingMut.Unlock()
|
||||
|
||||
c.receiver.Closed(c, err)
|
||||
// No more sends are necessary, therefore closing the underlying
|
||||
// connection can happen at the same time as the internal cleanup.
|
||||
// And this prevents a potential deadlock due to calling c.receiver.Closed
|
||||
go c.commonClose(err)
|
||||
})
|
||||
}
|
||||
|
||||
// internalClose is called if there is an unexpected error during normal operation.
|
||||
func (c *rawConnection) internalClose(err error) {
|
||||
c.once.Do(func() {
|
||||
c.commonClose(err)
|
||||
})
|
||||
}
|
||||
|
||||
// commonClose is a utility function that must only be called from within
|
||||
// rawConnection.once.Do (i.e. in Close and close).
|
||||
func (c *rawConnection) commonClose(err error) {
|
||||
l.Debugln("close due to", err)
|
||||
close(c.closed)
|
||||
|
||||
c.awaitingMut.Lock()
|
||||
for i, ch := range c.awaiting {
|
||||
if ch != nil {
|
||||
close(ch)
|
||||
delete(c.awaiting, i)
|
||||
}
|
||||
}
|
||||
c.awaitingMut.Unlock()
|
||||
|
||||
c.receiver.Closed(c, err)
|
||||
}
|
||||
|
||||
// The pingSender makes sure that we've sent a message within the last
|
||||
// PingSendInterval. If we already have something sent in the last
|
||||
// PingSendInterval/2, we do nothing. Otherwise we send a ping message. This
|
||||
@@ -859,7 +890,7 @@ func (c *rawConnection) pingReceiver() {
|
||||
d := time.Since(c.cr.Last())
|
||||
if d > ReceiveTimeout {
|
||||
l.Debugln(c.id, "ping timeout", d)
|
||||
c.close(ErrTimeout)
|
||||
c.internalClose(ErrTimeout)
|
||||
}
|
||||
|
||||
l.Debugln(c.id, "last read within", d)
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestClose(t *testing.T) {
|
||||
c0.ClusterConfig(ClusterConfig{})
|
||||
c1.ClusterConfig(ClusterConfig{})
|
||||
|
||||
c0.close(errors.New("manual close"))
|
||||
c0.internalClose(errors.New("manual close"))
|
||||
|
||||
<-c0.closed
|
||||
if err := m0.closedError(); err == nil || !strings.Contains(err.Error(), "manual close") {
|
||||
|
||||
Reference in New Issue
Block a user