How to enforce concurrency in Golang gorilla WebSocket package

I looked into the Godoc of the gorilla / websocket package.

Godoc clearly states that

Concurrency connections support one concurrent reader and one concurrent writer.

Applications are responsible for at most one goroutine invoking write methods (NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel) at the same time and that no more than one goroutine invokes read methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler) ...

Close and WriteControl methods can be called simultaneously with all other Methods.

However, in one of the examples provided by the package

func (c *Conn) readPump() {
    defer func() {
        hub.unregister <- c
        c.ws.Close()
    }()
    c.ws.SetReadLimit(maxMessageSize)
    c.ws.SetReadDeadline(time.Now().Add(pongWait))
    c.ws.SetPongHandler(func(string) error { 
        c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil
    })
    for {
        _, message, err := c.ws.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))
        hub.broadcast <- message
    }
}

      

Source: https://github.com/gorilla/websocket/blob/a68708917c6a4f06314ab4e52493cc61359c9d42/examples/chat/conn.go#L50

This line

c.ws.SetPongHandler(func(string) error { 
    c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil
})

      

and this line

_, message, err := c.ws.ReadMessage()

      

seems to be out of sync because the first line is a callback function, so it must be called in the Goroutine created in the package and the second line is executed in the Goroutine that calls serveWs

More importantly, I must ensure that no more than one cause goroutine tags SetReadDeadline

, ReadMessage

, SetPongHandler

, SetPingHandler

?

I am trying to use a Mutex lock and block it whenever I call the above functions and then unlock it, but quickly I understand the problem. Usually (also in the example) what ReadMessage

is called in the for loop. But if Mutext is blocked before ReadMessage, then no other Read functions can acquire the lock and execute until the next message is received.

Is there a better way to deal with this concurrency problem? Thanks in advance.

+3


source to share


1 answer


The best way to ensure there are no concurrent calls to the read methods is to execute all the read methods from the same version of goroutine.

All Gorilla web app examples use this approach, including the example inserted in the question. In this example, all calls to the read methods refer to the method readPump

. The method readPump

is called once to connect on one goroutine. It follows that the read methods do not call the connection at the same time.



The documentation section on control messages states that the application should read the connection to process control messages. Based on this and Gorilla's own examples, I think it's safe to assume that ping, pong, and tight handlers will be called from an application reading goroutine, as is the case in the current implementation. It would be nice if the documentation could be more explicit. Maybe a problem with the file?

+1


source







All Articles