Skip to content

Commit a79bd14

Browse files
committed
Avoid mutating the channel map while ranging over it in Connection.Shutdown().
1 parent 6063341 commit a79bd14

File tree

2 files changed

+22
-13
lines changed

2 files changed

+22
-13
lines changed

connection.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -381,38 +381,42 @@ func (c *Connection) shutdown(err *Error) {
381381

382382
c.destructor.Do(func() {
383383
c.m.Lock()
384-
closes := make([]chan *Error, len(c.closes))
385-
copy(closes, c.closes)
386-
c.m.Unlock()
384+
defer c.m.Unlock()
385+
387386
if err != nil {
388-
for _, c := range closes {
387+
for _, c := range c.closes {
389388
c <- err
390389
}
391390
}
392391

393-
for _, ch := range c.channels {
394-
c.closeChannel(ch, err)
395-
}
396-
397392
if err != nil {
398393
c.errors <- err
399394
}
400395
// Shutdown handler goroutine can still receive the result.
401396
close(c.errors)
402397

403-
c.conn.Close()
404-
405-
for _, c := range closes {
398+
for _, c := range c.closes {
406399
close(c)
407400
}
408401

409402
for _, c := range c.blocks {
410403
close(c)
411404
}
412405

413-
c.m.Lock()
406+
// Shutdown the channel, but do not use closeChannel() as it calls
407+
// releaseChannel() which requires the connection lock.
408+
//
409+
// Ranging over c.channels and calling releaseChannel() that mutates
410+
// c.channels is racy - see commit 6063341 for an example.
411+
for _, ch := range c.channels {
412+
ch.shutdown(err)
413+
}
414+
415+
c.conn.Close()
416+
417+
c.channels = map[uint16]*Channel{}
418+
c.allocator = newAllocator(1, c.Config.ChannelMax)
414419
c.noNotify = true
415-
c.m.Unlock()
416420
})
417421
}
418422

connection_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net"
1313
"sync"
1414
"testing"
15+
"time"
1516
)
1617

1718
func TestRequiredServerLocale(t *testing.T) {
@@ -68,6 +69,8 @@ func TestChannelOpenOnAClosedConnectionFails_ReleasesAllocatedChannel(t *testing
6869
// See https://github.com/streadway/amqp/issues/251 - thanks to jmalloc for the
6970
// test case.
7071
func TestRaceBetweenChannelAndConnectionClose(t *testing.T) {
72+
defer time.AfterFunc(10*time.Second, func() { panic("Close deadlock") }).Stop()
73+
7174
conn := integrationConnection(t, "allocation/shutdown race")
7275

7376
go conn.Close()
@@ -88,6 +91,8 @@ func TestRaceBetweenChannelAndConnectionClose(t *testing.T) {
8891
// See https://github.com/streadway/amqp/pull/253#issuecomment-292464811 for
8992
// more details - thanks to jmalloc again.
9093
func TestRaceBetweenChannelShutdownAndSend(t *testing.T) {
94+
defer time.AfterFunc(10*time.Second, func() { panic("Close deadlock") }).Stop()
95+
9196
conn := integrationConnection(t, "channel close/send race")
9297
defer conn.Close()
9398

0 commit comments

Comments
 (0)