streampack の Tana です。

streampack ではライブの一機能の一つで Websocket を使ってユーザーとインタラクティブなやり取り機能を提供してます。

Websocket はコネクションを貼り続けるため、 Maximum接続数の閾値テストしているとたまに落ちることがありました。

goroutine 5774 [running]:
runtime.throw(0xe2c371, 0x15)
/usr/lib/golang/src/runtime/panic.go:605 +0x95 fp=0xc4234a76f0 sp=0xc4234a76d0 pc=0x42c0d5
runtime.mapassign_fast64ptr(0xcfcc20, 0xc420320180, 0xc423490280, 0xc42334dc00)
/usr/lib/golang/src/runtime/hashmap_fast.go:695 +0x3d2 fp=0xc4234a7750 sp=0xc4234a76f0 pc=0x40e452
main.websocketHandler(0x7fd1be198e18, 0xc42325d3f0, 0xc42334dc00)
... 

急に負荷をかけすぎたため、たまたまかなと思いつつも、また再現。

エラーも大量にエラーが出て追いにくく、うーんと悩んでたところ、

fatal error: concurrent map writes

と出てました。

どうも、同時に map を書き込みに関するエラーのようです

よくよく調べてみると、

Maps are not safe for concurrent use: it’s not defined what happens when you read and > write to them simultaneously. If you need to read from and write to a map from concurrently executing goroutines, the accesses must be mediated by some kind of synchronization mechanism. One common way to protect maps is with sync.RWMutex.

ソース: source: https://blog.golang.org/go-maps-in-action

他にも同様な経験されたケースが多々ありました。

つまり、map を操作する場合は sync.RWMutex などのライブラリを使って、
Read/Write する際は排他的に Lock/Unlock する必要がありました。

var clients = make(map[*websocket.Conn]bool)
var ClientMutex struct {
  sync.Mutex
}

ClientMutex.Lock()  // ロック
clients[ws] = true
ClientMutex.Unlock() // アンロック

また、下記のように -race オプションをつけると Warning でアドバイスしてくれます。

$ go run -race main.go

goroutine 内でごにょごにょ map を Read/Write している場合は注意しましょう。

元記事はこちら

Go Panic でパニック