tmc / grpc-websocket-proxy

A proxy to transparently upgrade grpc-gateway streaming endpoints to use websockets
MIT License
552 stars 72 forks source link

srv.Context().Done() is not fired when WebSocket client disconnects? #22

Closed brocaar closed 3 years ago

brocaar commented 4 years ago

I'm running into an issue, and I hope you can help me. I'm using your library to stream events published to a Redis server to a WebSocket client (browser).

func (a *DeviceAPI) StreamEventLogs(req *pb.StreamDeviceEventLogsRequest, srv pb.DeviceService_StreamEventLogsServer) error {
    var devEUI lorawan.EUI64

    // For debugging to see when the request has been cancelled
    go func() {
        fmt.Println("cancelled!", <-srv.Context().Done())
    }()

    if err := devEUI.UnmarshalText([]byte(req.DevEui)); err != nil {
        return grpc.Errorf(codes.InvalidArgument, "devEUI: %s", err)
    }

    if err := a.validator.Validate(srv.Context(),
        auth.ValidateNodeAccess(devEUI, auth.Read)); err != nil {
        return grpc.Errorf(codes.Unauthenticated, "authentication failed: %s", err)
    }

    eventLogChan := make(chan eventlog.EventLog)
    go func() {
        err := eventlog.GetEventLogForDevice(srv.Context(), devEUI, eventLogChan)
        if err != nil {
            log.WithError(err).Error("get event-log for device error")
        }
        close(eventLogChan)
    }()

    for el := range eventLogChan {
        b, err := json.Marshal(el.Payload)
        if err != nil {
            return grpc.Errorf(codes.Internal, "marshal json error: %s", err)
        }

        resp := pb.StreamDeviceEventLogsResponse{
            Type:        el.Type,
            PayloadJson: string(b),
        }

        err = srv.Send(&resp)
        if err != nil {
            log.WithError(err).Error("error sending event-log response")
        }
    }

    return nil
}

In short, eventlog.GetEventLogForDevice is executed within a Go routine and blocks until the given context (srv.Context()) is cancelled. This function publishes to eventLogChan. When it completes (with or without error), it closes eventLogChan.

Below there is a loop which reads from eventLogChan and writes this to srv.


What I noticed is that when my WebSocket client disconnects, srv.Context().Done() does not fire. So the following code does not print:

    go func() {
        fmt.Println("cancelled!", <-srv.Context().Done())
    }()

Only when srv.Send() is called (e.g. there is a new event to send to the WebSocket client), srv.Context().Done() fires (which will cancel GetEventLogForDevice).

Is this an issue with grpc-websocket-proxy (e.g. the WebSocket disconnect event is not correctly captured), or should I have implemented my code in a different way?

brocaar commented 4 years ago

Please see #23 for what I believe would be the fix for this issue.