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 している場合は注意しましょう。