gRPC Unary Call Connection State Check on Server - go

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

Related

Using `Context` to implement timeout

Assuming that I have a function that sends web requests to an API endpoint, I would like to add a timeout to the client so that if the call is taking too long, the operation breaks either by returning an error or panicing the current thread.
Another assumption is that, the client function (the function that sends web requests) comes from a library and it has been implemented in a synchronous way.
Let's have a look at the client function's signature:
func Send(params map[string]string) (*http.Response, error)
I would like to write a wrapper around this function to add a timeout mechanism. To do that, I can do:
func SendWithTimeout(ctx context.Context, params map[string]string) (*http.Response, error) {
completed := make(chan bool)
go func() {
res, err := Send(params)
_ = res
_ = err
completed <- true
}()
for {
select {
case <-ctx.Done():
{
return nil, errors.New("Cancelled")
}
case <-completed:
{
return nil, nil // just to test how this method works
}
}
}
}
Now when I call the new function and pass a cancellable context, I successfully get a cancellation error, but the goroutine that is running the original Send function keeps on running to the end.
Since, the function makes an API call meaning that establishing socket/TCP connections are actually involved in the background, it is not a good practice to leave a long-running API behind the scene.
Is there any standard way to interrupt the original Send function when the context.Done() is hit?
This is a "poor" design choice to add context support to an existing API / implementation that did not support it earlier. Context support should be added to the existing Send() implementation that uses it / monitors it, renaming it to SendWithTimeout(), and provide a new Send() function that takes no context, and calls SendWithTimeout() with context.TODO() or context.Background().
For example if your Send() function makes an outgoing HTTP call, that may be achieved by using http.NewRequest() followed by Client.Do(). In the new, context-aware version use http.NewRequestWithContext().
If you have a Send() function which you cannot change, then you're "out of luck". The function itself has to support the context or cancellation. You can't abort it from the outside.
See related:
Terminating function execution if a context is cancelled
Is it possible to cancel unfinished goroutines?
Stopping running function using context timeout in Golang
cancel a blocking operation in Go

Does WithContext method need to panic if context is nil?

I want to write a WithContext method for a struct and am taking inspiration from net/http's Request.WithContext.
My question is: why does Request.WithContext panic if the context is nil:
func (r *Request) WithContext(ctx context.Context) *Request {
if ctx == nil {
panic("nil context")
}
...
}
And should mine as well?
For more context on why I want to create a WithContext method: I am implementing an interface that does not provide a context parameter in its signature but believe the implementation requires it.
More specifically, I am writing a Redis backend for gorilla/session using the official Redis client for Go, where the Get and Set methods take context.Context.
The idea is that my redis store will be shallow copied with the new context object, when needed, and then used:
type redisStore struct {
codecs []securecookie.Codec
backend Backend // custom interface for Redis client
options *sessions.Options
ctx context.Context
}
func (s *redisStore) WithContext(ctx context.Context) *redisStore {
if ctx == nil {
panic("nil context")
}
s2 := new(redisStore)
*s2 = *s
s2.ctx = ctx
return s2
}
// Backend
type Backend interface {
Set(context.Context, string, interface{}) error
Get(context.Context, string) (string, error)
Del(context.Context, string) error
}
The purpose of panicking is to "fail fast" and reject a nil context without changing the function signature.
If the function does not panic then it must return error in order to reject a bad input:
func (r *Request) WithContext(ctx context.Context) (*Request, error) {
if ctx == nil {
return nil, errors.New("nil ctx")
}
...
}
And then who calls this function must handle the error to avoid using an invalid request:
request, err = request.WithContext(nil)
if err != nil {
}
By handling the error you are introducing a control flow branch, and you lose method chaining. You also cannot immediately use WithContext return value into a function parameter:
// cannot do, because WithContext returns an error too
data, err := fetchDataWithContext(request.WithContext(ctx), otherParam)
Also it would create an error instance that will be eventually garbage collected. This all is cumbersome, poor usability and unnecessary alloc simply for saying "don't give me a nil context".
About creating a redis store with a context, the context documentation is clear:
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
The important detail is request-scoped. So setting a context in the redis client itself is contrary to this recommendation. You should pass context values at each get/set call.
The context of an HTTP request is canceled if the client closes the connection. When the context is canceled, all its child contexts are also canceled, so a nil context would panic then. Because of this, you cannot pass a nil context to WithContext.
Whether or not your redis store should panic depends on how you are going to use that context. It is usually not a good idea to include a context in a struct. One acceptable way of doing that is if the struct itself is a context. Contexts should be created for each call, should live for the duration of that call, and then thrown away.

context.Err() on complete

I am making multiple RPC calls to my server where the handler looks like:
func (h *handler) GetData(ctx context.Context, request Payload) (*Data, error) {
go func(ctx context.Context) {
for {
test := 0
select {
case <-ctx.Done():
if ctx.Err() == context.Canceled {
log.Info(ctx.Err())
test = 1
break
}
}
if test == 1 {
break
}
}
}(ctx)
data := fetchData(request)
return data, nil
}
the fetchData API takes around 5 seconds to get data and reply back to my service. Meanwhile if the client requests again, then I abort the old request and fire a new request. The abort is not visible on context object.
Rather ctx.Err() shows a value of context.Canceled even when the calls are not cancelled and end gracefully with expected data.
I am new to Go and don't understand how exactly context manages Cancels, timeout and completion.
Some insight on the behaviour will be helpful.
From the docs (emphasize mine):
For incoming server requests, the context is canceled when the client's connection closes, the request is canceled (with HTTP/2), or when the ServeHTTP method returns.
In other words, cancellation does not necessarly mean that the client aborted the request.
Contexts that can be canceled must be canceled eventually, and the HTTP server takes care of that:
Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.
What you observe works as intended.

Is there anyway to close client request in golang/gin?

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

MGO and long running Web Services - recovery

I've written a REST web service that uses mongo as the backend data store. I was wondering at this stage (before deployment), what the best practices were, considering a service that essentially runs forever(ish).
Currently, I'm following this type of pattern:
// database.go
...
type DataStore struct {
mongoSession *mgo.Session
}
...
func (d *DataStore) OpenSession () {
... // read setup from environment
mongoSession, err = mgo.Dial(mongoURI)
if err != nil {}
...
}
func (d *DataStore) CloseSession() {...}
func (d *DataStore) Find (...) (results...) {
s := d.mongoSession.Copy()
defer s.Close()
// do stuff, return results
}
In main.go:
func main() {
ds := NewDataStore()
ds.OpenSession()
defer ds.CloseSession()
// Web Service Routes..
...
ws.Handle("/find/{abc}", doFindFunc)
...
}
My question is - what's the recommended practice for recovery from session that has timed out, lost connection (the mongo service provider I'm using is remote, so I assume that this will happen), so on any particular web service call, the database session may no longer work? How do people handle these cases to detect that the session is no longer valid and a "fresh" one should be established?
Thanks!
what you may want is to do the session .Copy() for each incoming HTTP request (with deffered .Close()), copy again from the new session in your handlers if ever needed..
connections and reconnections are managed by mgo, you can stop and restart MongoDB while making an HTTP request to your web service to see how its affected.
if there's a db connection problem while handling an HTTP request, a db operation will eventually timeout (timeout can be configured by using DialWithTimeout instead of the regular Dial, so you can respond with a 5xx HTTP error code in such case.

Resources