I am using Fiber to develop a backend. I have a map that is a global variable that holds the socket connections. When I use the global variable from the same package, no problem here, everything works fine. But, when I try to use the sockets from a route function, I am getting the error below.
I tried to use mutex.lock but no luck.
I checked the code, the socket is not nil in my sendToAll method but it becomes nil in the helper method( inside the lib: github.com/fasthttp/websocket.(*Conn).WriteMessage )
Any advice is welcome.
Thanks.
type ConnectedSocketsContainerType struct {
M sync.Mutex
ConnectedSockets map[string]*websocket.Conn
}
var ConnectedSocketsContainer = ConnectedSocketsContainerType{ M:sync.Mutex{} , ConnectedSockets: make(map[string]*websocket.Conn) }
In another package in GET request handler calls that method:
func send(socketID string,message string) {
sockethub.ConnectedSocketsContainer.M.Lock()
sendToAll(message)
sockethub.ConnectedSocketsContainer.M.Unlock()
}
func sendToAll(message string) {
for k := range sockethub.SocketsIDs {
k.WriteMessage(1, []byte(message))
}
}
The error:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x14d27f8]
goroutine 6 [running]:
github.com/fasthttp/websocket.(*Conn).WriteMessage(0xc00006aa78, 0xc00045e115, {0xc0004540f0, 0x29, 0xc00006ab2f})
/Users/emre/go/pkg/mod/github.com/fasthttp/websocket@v1.4.3-rc.10/conn.go:753 +0x38
goserver/controllers/api.sendToAll({0xc00045e115, 0x29})
/Users/emre/Documents/Free/SocketServer/goServer/controllers/api/socket.go:11 +0xac
goserver/controllers/api.send({0xc000456000, 0x15edfe1}, {0xc00045e115, 0x0})
/Users/emre/Documents/Free/SocketServer/goServer/controllers/api/socket.go:22 +0x65
goserver/controllers/api.SendMessageController(0xc000128a50)
/Users/emre/Documents/Free/SocketServer/goServer/controllers/api/socket.go:29 +0x71
github.com/gofiber/fiber/v2.(*App).next(0xc00019cb60, 0xc000456000)
/Users/emre/go/pkg/mod/github.com/gofiber/fiber/v2@v2.23.0/router.go:127 +0x1d8
github.com/gofiber/fiber/v2.(*App).handler(0xc00019cb60, 0x10bb517)
/Users/emre/go/pkg/mod/github.com/gofiber/fiber/v2@v2.23.0/router.go:155 +0xe5
github.com/valyala/fasthttp.(*Server).serveConn(0xc000126000, {0x16c4fa0, 0xc0000106e8})
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp@v1.31.0/server.go:2278 +0x122d
github.com/valyala/fasthttp.(*workerPool).workerFunc(0xc00014c000, 0xc00022dba0)
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp@v1.31.0/workerpool.go:223 +0xa9
github.com/valyala/fasthttp.(*workerPool).getCh.func1()
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp@v1.31.0/workerpool.go:195 +0x38
created by github.com/valyala/fasthttp.(*workerPool).getCh
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp@v1.31.0/workerpool.go:194 +0x1b5
exit status 2
Full example for go server. Please see two comments that specify working and not working code blocks.
package main
import (
"fmt"
"sync"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
func main() {
app := fiber.New()
ListenSocket(app)
app.Get("/socket/send", SendMessageController )
app.Listen(":3000")
}
const websocketHeaderKey = "Sec-Websocket-Key"
var ConnectedIDSockets sync.Map
func SendMessageController( c *fiber.Ctx ) error {
ConnectedIDSockets.Range(func(key, value interface{}) bool {
c := value.(*websocket.Conn)
if c == nil {
// that line is not printed, c is not nil.
fmt.Println("c is nil.")
return true
}
// we have crash at that line, even we read the err.
err := c.WriteMessage(websocket.TextMessage, []byte("message"))
// program does not runs to here since it crashed.
println("err:", err)
return true
})
return nil
}
func ListenSocket(app *fiber.App) {
app.Use("/ws", func(c *fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
c.Locals(websocketHeaderKey, string(c.Request().Header.Peek(websocketHeaderKey)))
return c.Next()
}
return fiber.ErrUpgradeRequired
})
app.Get("/ws/:projectKEY", websocket.New(func(c *websocket.Conn) {
socketID := c.Locals(websocketHeaderKey).(string)
ConnectedIDSockets.Store(socketID, c)
// that works.
conn, _ := ConnectedIDSockets.Load(socketID)
socketmap := conn.(*websocket.Conn)
socketmap.WriteMessage(1, []byte(socketID))
}))
}
This panic is confusing because there are actually two packages called websocket
. One in github.com/gofiber/websocket/v2
and another one in github.com/fasthttp/websocket
, and both have their own *websocket.Conn
. However the websocket.Conn
in github.com/gofiber/websocket
actually embeds the websocket.Conn
from github.com/fasthttp/websocket
(I know, terrible design) making what's going on unclear.
Your call to c.WriteMessage
is actually going to c.Conn.WriteMessage
, and c.Conn
is what's nil. So in your nil check, you actually need to do if c == nil || c.Conn == nil {
to check the embedded struct as well.