lib/protocol: Fix potential deadlock when closing connection (ref #5440) (#5442)

This commit is contained in:
Simon Frei 2019-01-14 08:32:37 +01:00 committed by Jakob Borg
parent 51f65bd23a
commit c87411851d
1 changed files with 29 additions and 37 deletions

View File

@ -171,11 +171,12 @@ type rawConnection struct {
nextID int32 nextID int32
nextIDMut sync.Mutex nextIDMut sync.Mutex
outbox chan asyncMessage outbox chan asyncMessage
sendClose chan asyncMessage closed chan struct{}
closed chan struct{} closeOnce sync.Once
once sync.Once sendCloseOnce sync.Once
compression Compression writerExited chan struct{}
compression Compression
} }
type asyncResult struct { type asyncResult struct {
@ -216,7 +217,6 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
cw: cw, cw: cw,
awaiting: make(map[int32]chan asyncResult), awaiting: make(map[int32]chan asyncResult),
outbox: make(chan asyncMessage), outbox: make(chan asyncMessage),
sendClose: make(chan asyncMessage),
closed: make(chan struct{}), closed: make(chan struct{}),
compression: compress, compression: compress,
} }
@ -643,11 +643,6 @@ func (c *rawConnection) writerLoop() {
return return
} }
case m := <-c.sendClose:
c.writeMessage(m)
close(m.done)
return // No message must be sent after the Close message.
case <-c.closed: case <-c.closed:
return return
} }
@ -813,41 +808,38 @@ func (c *rawConnection) shouldCompressMessage(msg message) bool {
// BEP message is sent before terminating the actual connection. The error // BEP message is sent before terminating the actual connection. The error
// argument specifies the reason for closing the connection. // argument specifies the reason for closing the connection.
func (c *rawConnection) Close(err error) { func (c *rawConnection) Close(err error) {
c.once.Do(func() { c.sendCloseOnce.Do(func() {
done := make(chan struct{}) done := make(chan struct{})
c.sendClose <- asyncMessage{&Close{err.Error()}, done} c.send(&Close{err.Error()}, done)
<-done select {
case <-done:
// No more sends are necessary, therefore closing the underlying case <-c.closed:
// 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)
}) })
// No more sends are necessary, therefore further steps to close the
// connection outside of this package can proceed immediately.
// And this prevents a potential deadlock due to calling c.receiver.Closed
go c.internalClose(err)
} }
// internalClose is called if there is an unexpected error during normal operation. // internalClose is called if there is an unexpected error during normal operation.
func (c *rawConnection) internalClose(err error) { func (c *rawConnection) internalClose(err error) {
c.once.Do(func() { c.closeOnce.Do(func() {
c.commonClose(err) l.Debugln("close due to", err)
}) close(c.closed)
}
// commonClose is a utility function that must only be called from within c.awaitingMut.Lock()
// rawConnection.once.Do (i.e. in Close and close). for i, ch := range c.awaiting {
func (c *rawConnection) commonClose(err error) { if ch != nil {
l.Debugln("close due to", err) close(ch)
close(c.closed) delete(c.awaiting, i)
}
c.awaitingMut.Lock()
for i, ch := range c.awaiting {
if ch != nil {
close(ch)
delete(c.awaiting, i)
} }
} c.awaitingMut.Unlock()
c.awaitingMut.Unlock()
c.receiver.Closed(c, err) c.receiver.Closed(c, err)
})
} }
// The pingSender makes sure that we've sent a message within the last // The pingSender makes sure that we've sent a message within the last