I'm having hard time understanding how references in go work. I want to write a really simple in-memory publish-subscribe mechanism. Here's the code:
package sockets
import (
"fmt"
"github.com/gorilla/websocket"
)
type hubSingleton struct {
Clients map[string][]*websocket.Conn
}
var instance *hubSingleton
func Hub() *hubSingleton {
if instance == nil {
fmt.Println("New instance created")
instance = &hubSingleton{}
}
instance.Clients = make(map[string][]*websocket.Conn, 6)
return instance
}
func (hub *hubSingleton) Subscribe(channel string, socket *websocket.Conn) error {
if _, ok := hub.Clients[channel]; !ok {
hub.Clients[channel] = make([]*websocket.Conn, 0)
}
hub.Clients[channel] = append(hub.Clients[channel], socket)
fmt.Println("Subscribe: ", hub.Clients)
return nil
}
func (hub *hubSingleton) Publish(channel string, message interface{}) {
fmt.Println("Publish: ", hub.Clients)
if _, ok := hub.Clients[channel]; !ok {
return
}
for i := 0; i < len(hub.Clients[channel]); i++ {
conn := hub.Clients[channel][i]
conn.WriteJSON(Message{status: "ok", content: message})
}
}
The problem is that it seems that every time I call Hub().Publish() or Hub().Subscribe() a new hubSingleton.Client is created. Well, I'm not sure what happens, but here's the output of running program:
Publish: map[]
Subscribe: map[chan:[0xc820170000]]
Publish: map[]
Subscribe: map[chan:[0xc82008ac80]]
Publish: map[]
Publish: map[]
Publish: map[]
Subscribe: map[chan:[0xc820170140]]
Publish: map[]
Subscribers added to the map disapear every time I call Hub(). What can I do, to make the changes in map be preserved between calls?
The Hub
function creates a new client map on every call. Change the function to:
func Hub() *hubSingleton {
if instance == nil {
fmt.Println("New instance created")
instance = &hubSingleton{}
instance.Clients = make(map[string][]*websocket.Conn, 6)
}
return instance
}
If the first call to Hub
is from a request handler, then there's a data race on instance
. Use a lock to fix the race:
var (
instance *hubSingleton
mu sync.Mutex
)
func Hub() *hubSingleton {
mu.Lock()
defer mu.Unlock()
if instance == nil {
fmt.Println("New instance created")
instance = &hubSingleton{}
instance.Clients = make(map[string][]*websocket.Conn, 6)
}
return instance
}
A simpler approach is to initialize the instance once before use:
var instance *hubSingleton
func newHub() *hubSingleton {
return &hubSingleton{Clients: make(map[string][]*websocket.Conn, 6)}
}
func main() {
instance = newHub()
...
}
If Publish
and Subscribe
are called concurrently by handlers, then there are data races on Clients
in Publish
and Subscribe
.