The context.WithTimeout is used in the grpc.DialContext to control the timeout of all RPC calls of the current DialContext. It is inconvenient to handle the different timeouts of one/some specific RPC call.
We could define one custom timeout callOption to handle the forced timeout value of some RPC calls in the clientInterceptor
Define the custom timeout callOption through EmptyCallOption
type TimeoutCallOption struct {
grpc.EmptyCallOption
forcedTimeout time.Duration
}
func WithForcedTimeout(forceTimeout time.Duration) TimeoutCallOption {
return TimeoutCallOption{forcedTimeout: forceTimeout}
}
Handle the forcedTimeout in the UnaryClientInterceptor
func getForcedTimeout(callOptions []grpc.CallOption) (time.Duration, bool) {
for _, opt := range callOptions {
if co, ok := opt.(TimeoutCallOption); ok {
return co.forcedTimeout, true
}
}
return 0, false
}
func TimeoutInterceptor(t time.Duration) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
timeout := t
if v, ok := getForcedTimeout(opts); ok {
timeout = v
}
if timeout <= 0 {
return invoker(ctx, method, req, reply, cc, opts...)
}
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return invoker(ctx, method, req, reply, cc, opts...)
}
}
Usage samples
// The default timeout of RPC call of this conn is 3 seconds
conn, err := grpc.Dial(
address,
grpc.WithUnaryInterceptor(TimeoutInterceptor(time.Duration(3)*time.Second)), ...)
....
c := pb.NewGreeterClient(conn)
c.SayHello(context.Background(), &pb.HelloRequest{Name: "world"},
WithForcedTimeout(time.Duration(10)*time.Second))
// The timeout of SayHello RPC call is 10 seconds
MaxDelaymay not be correct, you need to set the Time out of deadline, so that the connection throw error / exception on timeout, check the links grpc.io/blog/deadlines