Help requested on cross go routine communication

I’ve been slowly learning go by making changes to the gorilla websocket Chat demo project -https://github.com/gorilla/websocket/tree/master/examples/chat. It’s been really fun but I’ve hit a bit of a barrier regarding channels and communicating across go routines.

In the example project, a Hub maintains a list of connected clients, is run as a single go routine and exposes channels for adding clients, removing clients and broadcasting messages to all clients. For each connected Client, 2 go routines are run to read messages and write messages. I think I get all that and can follow the code.

However, I’ve modified the Client struct to contain a unique clientId (provided as a web socket message after the client has connected). If the same clientId makes a second connection, I want to close that connection, but I’m not sure how to implement a synchronous call between the Hub go routine and the Client read go routine?

My first attempt was to add a new channel that the Client could send the clientId across to indicate to the Hub to remove any existing connections. I added a new unregisterId string channel and then sent the message from the client. The run method in Hub now looks like this:

...
func (h *Hub) run() {
	for {
		select {
		case client := <-h.register:
			h.clients[client] = true
		// New code here: receive clientId to unregister on a new channel
		case clientId := <-h.unregisterId:
			for client := range h.clients { 
				if client.clientId == clientId {
					delete(h.clients, client)
					close(client.send)
				}
			}
		case client := <-h.unregister:
			if _, ok := h.clients[client]; ok {
				delete(h.clients, client)
				close(client.send)
			}
	
...

And the readPump function in Client was modified to:

...
for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
				log.Printf("error: %v", err)
			}
			break
		}
		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		
		//clientId read from JSON message
		c.hub.unregisterId <- clientId
		
		//Assume that any previous client with the same ID has now been disconnected
		c.clientId = clientId
		
...

However, this obviously doesn’t work as the client go routine just carries on after sending the message to the unregisterId channel. The Hub go routine then resumes and closes the connection as it has now has the same clientId that was sent down the channel.

What I want is for the client go routine to block until the Hub has finished processing the message from the channel. From reading around, I think my options are:

  • Call a new function on the Hub (from the Client go routine) to remove the existing connections and use a Mutex to lock the client map in the Hub.

  • (Somehow) pass a WaitGroup to the Hub (via the channel?) and use that from the Client go routine to wait for the Hub to remove any existing clients

  • Signal back to the Client from the Hub using another channel once any existing connections have been removed (but again, I’ve no idea how I would make this channel available to the Hub)

Any suggestions or pointers on how best to approach this would be very much welcomed!

1 Like

I think all three options could work. Regarding the third one, you can send a channel through a channel, in order to pass a back channel to a goroutine. Example:

package main

import "fmt"

func client(c chan chan string) {
    ch := <-c     // receive back channel
    ch <- "Done"  // send reply through back channel
}

func main() {
    chanChan := make(chan chan string)
    replyChan := make(chan string)
    go client(chanChan)      // pass chan of chan to goroutine
    chanChan <- replyChan    // send back channel to goroutine
    fmt.Println(<-replyChan) // read reply from back channel
}

(Playground link)

1 Like

Thanks for the reply Christopher, that’s really helpful, I didn’t think about passing a channel via a channel. Was worried that I have might have been missing an obvious solution that was better than the 3 options I identified.

You’re welcome! :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.