Skip to content

Commit adca050

Browse files
fix: prevent raccoon restart by adding RWMutex for connection group count map (#14)
Added RWMutex protection around the connection group counter map to ensure concurrent safety while reporting the metric on the number of connections. It will solve the runtime crash due to concurrent map iteration and map write
1 parent 32a0859 commit adca050

3 files changed

Lines changed: 31 additions & 16 deletions

File tree

services/rest/service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ func reportConnectionMetrics(conn connection.Table) {
5454
t := time.Tick(config.MetricStatsd.FlushPeriodMs)
5555
for {
5656
<-t
57-
for k, v := range conn.TotalConnectionPerGroup() {
57+
conn.RangeConnectionPerGroup(func(k string, v int) {
5858
metrics.Gauge("connections_count_current", v, fmt.Sprintf("conn_group=%s", k))
59-
}
59+
})
6060
}
6161
}
6262

services/rest/websocket/connection/table.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ func (t *Table) TotalConnection() int {
8383
return len(t.connMap)
8484
}
8585

86-
func (t *Table) TotalConnectionPerGroup() map[string]int {
87-
return t.counter
86+
func (t *Table) RangeConnectionPerGroup(fn func(string, int)) {
87+
t.m.RLock()
88+
defer t.m.RUnlock()
89+
for k, v := range t.counter {
90+
fn(k, v)
91+
}
8892
}

services/rest/websocket/connection/table_test.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,6 @@ import (
88
"github.com/stretchr/testify/assert"
99
)
1010

11-
func TestConnectionPerGroup(t *testing.T) {
12-
t.Run("Should return all the group on the table with the count", func(t *testing.T) {
13-
table := NewTable(10)
14-
table.Store(identification.Identifier{ID: "user1", Group: "group1"})
15-
table.Store(identification.Identifier{ID: "user2", Group: "group1"})
16-
table.Store(identification.Identifier{ID: "user3", Group: "group1"})
17-
table.Store(identification.Identifier{ID: "user1", Group: "group2"})
18-
table.Store(identification.Identifier{ID: "user2", Group: "group2"})
19-
assert.Equal(t, map[string]int{"group1": 3, "group2": 2}, table.TotalConnectionPerGroup())
20-
})
21-
}
22-
2311
func TestStore(t *testing.T) {
2412
t.Run("Should store new connection", func(t *testing.T) {
2513
table := NewTable(10)
@@ -146,6 +134,29 @@ func TestStoreBatch(t *testing.T) {
146134
})
147135
}
148136

137+
func TestRangeConnectionPerGroup(t *testing.T) {
138+
t.Run("Should iterate over all the group on the table with the count", func(t *testing.T) {
139+
//set up the table data
140+
table := NewTable(10)
141+
table.Store(identification.Identifier{ID: "user1", Group: "group1"})
142+
table.Store(identification.Identifier{ID: "user2", Group: "group1"})
143+
table.Store(identification.Identifier{ID: "user3", Group: "group1"})
144+
table.Store(identification.Identifier{ID: "user1", Group: "group2"})
145+
table.Store(identification.Identifier{ID: "user2", Group: "group2"})
146+
// Track callback calls
147+
called := make(map[string]int)
148+
table.RangeConnectionPerGroup(func(k string, v int) {
149+
called[k] = v
150+
})
151+
// Verify all keys are present
152+
assert.Equal(t, len(table.counter), len(called))
153+
// Verify values match
154+
for k, v := range table.counter {
155+
assert.Equal(t, v, called[k])
156+
}
157+
})
158+
}
159+
149160
func BenchmarkStoreBatch(b *testing.B) {
150161
table := NewTable(b.N)
151162
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)