Golang guidelines say that the goroutine that opens and writes to a channel should close it, but then how do I hook it into a websocket (like gorilla/websocket) handler goroutine?
func (server *Server) websocketUpgrade(responseWriter http.ResponseWriter, request *http.Request) {
// handle the request
connection, err := server.websocketUpgrader.Upgrade(responseWriter, request, responseHeaders)
defer connection.Close()
// now I need a channel that the server will write to
// so that the server can send messages to the client
// but it can not be created here because the server has to control it (write to it and close it)
for message := range messageChannel {
connection.WriteMessage(websocket.BinaryMessage, message)
}
}
What are the best practices to handle situations like this where I have no direct access to the goroutine that I need to assign a message channel for?
Related
I have an unary gRPC call that can take up to few minute to be processed on my Go gRPC server (involves human agent on the mobile APP). I would like to know if there is a way to check if the connection has been terminated on the client side before sending the response.
I found the solution for ServerStreaming case with Context Status.Done channel, but it does not work for my Unary RPC.
Below is the signature of the function where the control should be made:
func (*Server) EndpointName(ctx context.Context, in *pb.EndpointRequest) (*pb.EndpointResponse, error) {
As per the function definition shown in your question, the endpoint function is passed a context (ctx context.Context). If the connection drops the context will be cancelled.
For example I can modify the helloworld example so that it simulates a long running job:
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
select {
case <-ctx.Done():
fmt.Println("Context is done:", ctx.Err())
return nil, status.Error(codes.Canceled, "does not matter as nothing will ever get this anyway...")
case <-time.After(time.Minute):
// This simulates a long-running process. In reality the process itself should be checking the context
}
return &pb.HelloReply{}, nil
}
To test this I altered greeter_client to call the function and then panic() whilst waiting for the response; the server outputs:
2022/08/08 08:16:57 server listening at [::]:50051
Context is done: context canceled
I am building service that needs to send events to all subscribed consumers in Pub/Sub manner eg. send one event to all currently connected clients.
I am using Protobuf for that with the following proto definition:
service EventsService {
rpc ListenForEvents (AgentProcess) returns (stream Event) {}
}
Both server & client are written in Go.
My problem is that when client initiates connection then the stream it is not long-lived, eg. when server returns from ListenForEvents method:
func (e EventsService) ListenForEvents(process *pb.AgentProcess, listener pb.EventsService_ListenForEventsServer) error {
//persist listener here so it can be used later when backend needs to send some messages to client
return nil
}
then the client almost instantly gets EOF error which means that server probably closed connection.
What do I do so that the client is subscribed for a long time to the server? The main problem is that I might not have anything to send to the client when it calls ListenForEvents method on the server, this is why I want this stream to be long lived to be able to send messages later.
The stream terminates when you return from the server function. Instead, you should receive events somehow, and send them to the client without returning from your server. There are probably many ways you can do this. Below is the sketch of one way of doing it.
This relies on the server connection running on a separate goroutine. There is a Broadcast() function that will send messages to all connected clients. It looks like this:
var allRegisteredClients map[*pb.AgentProcess]chan Message
var clientsLock sync.RWMutex{}
func Broadcast(msg Message) {
clientsLock.RLock()
for _,x:=range allRegisteredClients {
x<-msg
}
clientsLock.RUnlock()
}
Then, your clients have to register themselves, and process messages:
func (e EventsService) ListenForEvents(process *pb.AgentProcess, listener pb.EventsService_ListenForEventsServer) error {
clientsLock.Lock()
ch:=make(chan Message)
allRegisteredClients[process]=ch
clientsLock.Unlock()
for msg:=range ch {
// send message
// Deal with errors
// Deal with client terminations
}
clientsLock.Lock()
delete(allRegisteredClients,process)
clientsLock.Unlock()
}
As I said, this is only a sketch of the idea.
I have managed to nail it down.
Basically I never return from method ListenForEvents.
It creates channel, persists in global-like map of subscribed clients and keeps reading from that channel indefinitely.
The whole implementation of server logic:
func (e EventsService) ListenForEvents(process *pb.AgentProcess, listener pb.EventsService_ListenForEventsServer) error {
chans, exists := e.listeners[process.Hostname]
chanForThisClient := make(chan *pb.Event)
if !exists {
e.listeners[process.Hostname] = []chan *pb.Event{chanForThisClient}
} else {
e.listeners[process.Hostname] = append(chans, chanForThisClient)
}
for {
select {
case <-listener.Context().Done():
return nil
case res := <-chanForThisClient:
_ = listener.Send(res)
}
}
return nil
}
You need to provide keepalive settings for grpc client and server
See details here https://github.com/grpc/grpc/blob/master/doc/keepalive.md
Examples https://github.com/grpc/grpc-go/tree/master/examples/features/keepalive
I have a web app whose server creates a Client for each websocket connection. A Client acts as an intermediary between the websocket connection and a single instance of a Hub. The Hub maintains a set of registered clients and broadcasts messages to the clients. This works pretty well but the problem is that a client might miss events between when the server generates the initial state bundle that the client receives on connection and when the client is registered with the hub and starts receiving broadcast events.
My idea is to register the client with the hub before any information is fetched from the db. That would ensure that the client doesn't miss any broadcasts, though now it could receive messages that are already applied to the initial state it receives. To allow the client to disregard these messages I could include a monotonic timestamp in both the initial state bundle as well as broadcast events.
Can you think of a more elegant/simpler solution?
I have used a write-ahead-log in the past to do something like this. In short, keep a ring buffer of messages in the hub. Then replay messages that where send to existing clients while the new one was initialized.
You can expose this concept to the clients too if you wish. That way you can implement efficient re-connects (particularly nice for mobile connections). When clients loose the websocket connection they can reconnect and say "Hey there, it's me again. Looks like we got interrupted. The last message I've seen was number 42. What's new?"
The following is from memory, so take this only as an illustration of the idea, not a finished implementation. In the intererest of brevity I've omited the select statements around client.send, for instance.
package main
import (
"container/list"
"sync"
"github.com/gorilla/websocket"
)
type Client struct { // all unchanged
hub *Hub
conn *websocket.Conn
send chan []byte
}
type Hub struct {
mu *sync.RWMutex
wal list.List // List if recent messages
clients map[*Client]bool // Registered clients.
register chan Registration // not a chan *Client anymore
broadcast chan []byte
unregister chan *Client
}
type Registration struct {
client *Client
// init is a function that is executed before the client starts to receive
// broadcast messages. All messages that are broadcast while init is
// running will be sent after init returns.
init func()
}
func (h *Hub) run() {
for {
select {
case reg := <-h.register:
// Take note of the most recent message as of right now.
// initClient will replay all later messages
h.mu.RLock()
head := h.wal.Back()
h.mu.RUnlock()
go h.initClient(reg, head)
case client := <-h.unregister:
h.mu.Lock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
h.mu.Unlock()
case message := <-h.broadcast:
h.mu.Lock()
h.wal.PushBack(message)
// TODO: Trim list if too long by some metric (e.g. number of
// messages, age, total message size, etc.)
clients := make([]*Client, len(h.clients))
copy(clients, h.clients)
h.mu.Unlock()
for client := range clients {
// TODO: deal with backpressure
client.send <- message
}
}
}
}
func (h *Hub) initClient(reg Registration, head *list.Element) {
reg.init()
// send messages in h.wal after head
for {
h.mu.RLock()
head = head.Next()
if head == nil {
// caught up
h.clients[reg.client] = true
h.mu.RUnlock()
return
}
h.mu.RUnlock()
// TODO: deal with backpressure
reg.client.send <- head.Value.([]byte)
}
}
I have a websocket server using gorilla/websocket.
I have a situation where I am simply writing messages to a set of websockets. My custom CloseHandler is never called when I close the websocket on the browser side.
However, adding a goroutine that calls ReadMessage indefinitely (till some error) leads to the CloseHandler being invoked.
Here's the basic idea:
In one goroutine, I run something like this:
for {
for client := range clients {
client.stream <- data
}
time.Sleep(time.Second)
}
and the other code, called in a separate goroutine, one per client:
go (func() {
// If I call wsock.ReadMessage here, my CloseHandler works!
})()
for msg := range myclient.stream {
if err := wsock.WriteMessage(websocket.TextMessage, msg); err != nil {
break
}
}
When I close the websocket on the browser side, I expect the CloseHandler to be called, however, it's never called, instead, I eventually get an error on the WriteMessage call.
The close handler is called when a close message is received from the peer. The application must read the connection to receive close and other control messages.
If the application does not read the connection or the peer does not send a close message, then the close handler will not be called.
If your goal is to detect closed connections, then read the connection until an error as returned as shown in the documentation:
func readLoop(c *websocket.Conn) {
for {
if _, _, err := c.NextReader(); err != nil {
c.Close()
break
}
}
}
The application should only set a close handler when the application must perform some action before the connection bounces the close message back to the peer.
Using gin framework.
Is there anyway to notify client to close request connection, then server handler can do any back-ground jobs without letting the clients to wait on the connection?
func Test(c *gin.Context) {
c.String(200, "ok")
// close client request, then do some jobs, for example sync data with remote server.
//
}
Yes, you can do that. By simply returning from the handler. And the background job you want to do, you should put that on a new goroutine.
Note that the connection and/or request may be put back into a pool, but that is irrelevant, the client will see that serving the request ended. You achieve what you want.
Something like this:
func Test(c *gin.Context) {
c.String(200, "ok")
// By returning from this function, response will be sent to the client
// and the connection to the client will be closed
// Started goroutine will live on, of course:
go func() {
// This function will continue to execute...
}()
}
Also see: Goroutine execution inside an http handler