Go сервер падает с nil pointer dereference при отключении клиента по websocket протоколу
Написал (вру, взял из примеров для fasthttp :-)) код для работы по websocket протоколу. При закрытии подключения со стороны читателя (использовал плагин websocket для хрома) мой код падает с этой ошибкой:
ws.conn.Close()
panic: runtime error: invalid memory address or nil pointer dereference
goroutine 96 [running]: github.com/fasthttp/websocket.(*Conn).Close(...)
При этом непонятно что там может быть nil - я ведь даже проверку сделал. Лог выглядит так:
ERRO[02/19 21:46:34] wshandler.go: 163 [HANDLER ] ws:txt:read::websocket: close 1005 (no status)
INFO[02/19 21:46:34] wshandler.go: 143 [WEBSOCKET] ws:reader:close
INFO[02/19 21:46:41] wshandler.go: 130 [WEBSOCKET] ws:ping
И далее там самая ошибка... использую fiber и middleware webscoket для него, который по сути обертка над fasthttp/websoсket, который обертка над gorilla/websocket.
Сам код работает нормально пока не закроешь подключение c клиента. P.S. Правильно обрабатывать ошибки websocket протокола пока не умею. Просьба пояснить, где я сильно натупил.
const (
// Time allowed to write a message to the peer.
writeWait = 30 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 15 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
)
func (ws *WS) writer() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
if ws.conn != nil {
ws.conn.Close()
// panic: runtime error: invalid memory address or nil pointer dereference
// goroutine 96 [running]: github.com/fasthttp/websocket.(*Conn).Close(...)
ws.t.Log().Infof(FmtWSInfo, "ws:writer:close")
}
}()
for {
select {
case msg, ok := <-ws.data:
ws.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
ws.t.Log().Errorf(FmtWSInfo, "ws:deadline")
ws.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
main, err := ws.t.translate(ws.params,msg)
if err != nil {
ws.t.log.Errorf(FmtHandlerError, "ws:txt:translate:", err)
return
}
payload := []byte(main.OutText)
if err = ws.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
ws.t.Log().Errorf(FmtHandlerError, "ws:txt:write:", err)
return
}
case <-ticker.C:
ws.t.Log().Infof(FmtWSInfo, "ws:ping")
ws.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
ws.t.Log().Errorf(FmtHandlerError, "ws:ping:", err)
return
}
}
}
}
func (ws *WS) reader() {
defer func() {
ws.t.Log().Infof(FmtWSInfo, "ws:reader:close")
ws.conn.Close()
}()
//fmt.Printf("%#v\n", ws)
//ws.conn.SetReadLimit(maxMessageSize)
ws.conn.SetReadDeadline(time.Now().Add(pongWait))
ws.conn.SetPongHandler(func(string) error {
ws.conn.SetReadDeadline(time.Now().Add(pongWait))
return nil
})
for {
_, msg, err := ws.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err,
websocket.CloseGoingAway,
websocket.CloseAbnormalClosure) {
ws.t.Log().Errorf(FmtHandlerError, "ws:txt:read:", err)
}
break
}
ws.data <- msg
}
}
type WS struct {
data chan []byte
conn *websocket.Conn
t *Translator
params map[string]string
}
func (t *Translator) WSHandler() fiber.Handler {
return websocket.New(func(conn *websocket.Conn) {
p := make(map[string]string)
p["lang"] = conn.Query("lang")
p["as"] = conn.Query("as")
p["format"] = conn.Query("format")
ws := &WS{
data: make(chan []byte),
conn: conn,
t: t,
params: p,
}
fmt.Printf("%#v\n", ws)
go ws.writer()
ws.reader()
})
}
Новый вариант writer, который не вызывает падения:
func (ws *WS) writer() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
if ws.conn.Conn != nil {
ws.conn.Conn.Close()
ws.t.Log().Infof(FmtWSInfo, "ws:writer:close")
}
}()
for {
select {
case msg, ok := <-ws.data:
//канал ws.data теперь закрывается в reader при выходе
if !ok {
ws.t.Log().Errorf(FmtWSInfo, "ws:writer:close chan")
if ws.conn.Conn == nil {
ws.t.Log().Errorf(FmtWSInfo, "ws:writer:connect nil")
return
}
ws.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
ws.conn.SetWriteDeadline(time.Now().Add(writeWait))
main, err := ws.t.translate(ws.params,msg)
if err != nil {
ws.t.log.Errorf(FmtHandlerError, "ws:txt:translate:", err)
return
}
payload := []byte(main.OutText)
if err = ws.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
ws.t.Log().Errorf(FmtHandlerError, "ws:txt:write:", err)
return
}
case <-ticker.C:
ws.t.Log().Infof(FmtWSInfo, "ws:ping")
ws.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
ws.t.Log().Errorf(FmtHandlerError, "ws:ping:", err)
return
}
}
}
}
Лог:
ERRO[02/20 00:09:27] wshandler.go: 170 [HANDLER ] ws:txt:read::websocket: close 1005 (no status)
INFO[02/20 00:09:27] wshandler.go: 150 [WEBSOCKET] ws:reader:close
ERRO[02/20 00:09:27] wshandler.go: 111 [WEBSOCKET] ws:writer:close chan
ERRO[02/20 00:09:27] wshandler.go: 113 [WEBSOCKET] ws:writer:connect nil