TLDR; If my Go gRPC bi-directional streaming server is looping on Recv/Send infinitely, how can I close the connection when my client's stream falls out of scope and gets GC'd?
Let's say I am using Go to do a gRPC bi-directional streaming call:
syntax = "proto3";
package mystical;
service Unicorn {
rpc RainbowStream(stream Rainbow) returns (stream Rainbow);
}
I have a server implementation that I will register with my gRPC server:
type Server struct {}
func (serv *Server) RainbowStream(stream proto.Unicorn_RainbowStreamServer) error {
// Stub implementation
// Just sends the rainbows back
for {
msg, err := stream.Recv()
if err != nil {
return err
}
if err := stream.Send(msg); err != nil {
return err
}
}
}
Then I have a client, eliding some of the gRPC nitty-gritty:
type Client struct {
proto.UnicornClient
}
func (c *Client) TastyColors() (stream proto.Unicorn_RainbowStreamClient, err error) {
return c.RainbowStream()
}
And I do this:
func somewhereDeepInMyCode() {
stream, err := aClient.TastyColors()
// ...
go func() {
stream.Send(msg)
// ...
// eventually this goroutine ends
// and `stream` would get GC'd
}
}
When stream falls out of scope, does the gRPC streaming call clean itself up? Normally, from what I understand, the server has to return on its RainbowStream call to close the connection. If it doesn't clean itself up, how can I achieve this?
Use a cancel (https://golang.org/pkg/context/#WithCancel) context to cancel from the client side:
func somewhereDeepInMyCode() {
ctx, cancel := context.WithCancel(context.Background())
stream, err := aClient.TastyColors(ctx)
// ...
go func() {
defer cancel()
stream.Send(msg)
// ...
// eventually this goroutine ends
// and `stream` would get GC'd
}
}
The server can handle client-side cancellation for cleanup as needed:
for {
select {
...
case <-stream.Context().Done():
// cleanup
...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With