Search code examples
gotimeoutgrpcconnection-timeoutgrpc-go

How does grpc.ConnectionTimeout() differ from context.WithTimeout(), and when should I use one over the other?


For grpc.ConnectionTimeout(), the Go GRPC docs mention:

ConnectionTimeout returns a ServerOption that sets the timeout for connection establishment (up to and including HTTP/2 handshaking) for all new connections. If this is not set, the default is 120 seconds.

Meanwhile, the docs about context.WithTimeout() say:

WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete

What I'm trying to do is configure timeouts on every GRPC request that hits a server, and wondering if grpc.ConnectionTimeout() or context.WithTimeout() is more appropriate.

Ideally, I want to configure a universal timeout for all requests of say, 5 seconds, and configure a smaller timeout of 100ms on requests that hit a specific path. How can I do this using either (or both) ConnectionTimeout() and context.WithTimeout() ?


Solution

  • ConnectionTimeout is about establishing HTTP connections. It is entirely independent of individual calls (i. e. requests).

    This isn't an either-or situation. They do different things and are not mutually exclusive.

    To implement a default timeout (or enforce a maximum timeout) for all RPCs write a UnaryClientInterceptor:

    import (
        "context"
        "time"
    
        "google.golang.org/grpc"
    )
    
    func DefaultTimeout(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
        if _, ok := ctx.Deadline(); !ok { // if you want to allow callers to extend the timeout
            var cancel context.CancelFunc
            ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
            defer cancel()
        }
    
        return invoker(ctx, method, req, reply, cc, opts...)
    }
    

    Something analogous can be done for StreamClientInterceptor but that seems counterproductive considering that streams are inherently long-lived.