gRPC gRPC service timeout setting

1. gRPC service timeout setting

The default request timeout of gRPC is very long. When you do not set the request timeout, all running requests will occupy a lot of resources and may

Running for a long time will lead to excessive service resource consumption, making subsequent request responses too slow, and even causing the entire process to crash.

To avoid this, our service should set a timeout. The previous introductory tutorial mentioned that when the client initiates a request, it needs to pass in the upper and lower

Text context.Context, used to end timeout or cancel request.

This article introduces how to set the timeout period for gRPC requests.

1.1 proto file and compilation

syntax = "proto3";

package proto;

option go_package = "./proto;proto";

// Define our services (multiple services can be defined, and each service can define multiple interfaces)
service Simple{
    rpc Route (SimpleRequest) returns (SimpleResponse){};
}

message SimpleRequest{
    // Define the parameters to be sent, using camel case naming method, lowercase and underlined, such as: student_name
    string data = 1;//Send data
}

message SimpleResponse{
    //Define the parameters received
    //Parameter type Parameter name Identification number (not repeatable)
    int32 code = 1; //Status code
    string value = 2;//Receive value
}
protoc --go_out=plugins=grpc:.simple.proto

1.2 Client request setting timeout

Modify the calling server method:

1. Set the timeout to the current time + 3 seconds

clientDeadline := time.Now().Add(time.Duration(3 * time.Second))
ctx, cancel := context.WithDeadline(ctx, clientDeadline)
defer cancel()

2. Add timeout detection in response error detection

//Input ctx with a timeout of 3 seconds
res, err := grpcClient.Route(ctx, & amp;req)
if err != nil {<!-- -->
    //Get error status
    status, ok := status.FromError(err)
if ok {<!-- -->
//Determine whether the call has timed out
if statu.Code() == codes.DeadlineExceeded {<!-- -->
log.Fatalln("Route timeout!")
}
}
log.Fatalf("Call Route err: %v", err)
}
// print return value
log.Println(res.Value)

3. Complete client.go code

package main

import (
"context"
pb "demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"log"
"time"
)

// Address connection address
const Address string = ":8000"

var grpcClient pb.SimpleClient

func main() {<!-- -->
\t// connect to the server
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {<!-- -->
log.Fatalf("net.Connect err: %v", err)
}
defer conn.Close()
ctx := context.Background()
// Establish gRPC connection
grpcClient = pb.NewSimpleClient(conn)
    //Set 2 seconds timeout
route(ctx, 2)
}

//route calls the server Route method
func route(ctx context.Context, deadlines time.Duration) {<!-- -->
//Set the timeout
clientDeadline := time.Now().Add(time.Duration(deadlines * time.Second))
ctx, cancel := context.WithDeadline(ctx, clientDeadline)
defer cancel()
//Create sending structure
req := pb.SimpleRequest{<!-- -->
Data: "grpc",
}
// Call our service (Route method)
// Pass in the ctx of the timeout
res, err := grpcClient.Route(ctx, & amp;req)
if err != nil {<!-- -->
//Get error status
status, ok := status.FromError(err)
if ok {<!-- -->
//Determine whether the call has timed out
if statu.Code() == codes.DeadlineExceeded {<!-- -->
log.Fatalln("Route timeout!")
}
}
log.Fatalf("Call Route err: %v", err)
}
// print return value
log.Println(res.Value)
}

1.3 The server determines whether the request has timed out

When the request times out, the server should stop ongoing operations to avoid wasting resources.

//Route implements the Route method
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {<!-- -->
data := make(chan *pb.SimpleResponse, 1)
go handle(ctx, req, data)
select {<!-- -->
case res := <-data:
return res, nil
case <-ctx.Done():
return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
}
}
func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {<!-- -->
select {<!-- -->
case <-ctx.Done():
log.Println(ctx.Err())
runtime.Goexit() //Exit the Go coroutine after timeout
case <-time.After(4 * time.Second): // Simulate time-consuming operations
res := pb.SimpleResponse{<!-- -->
Code: 200,
Value: "hello " + req.Data,
}
// Make timeout judgment before modifying the database
        // If it has timed out, exit
// if ctx.Err() == context.Canceled{}
data <- &res
}
}

Generally, timeout detection is performed before writing to the library, and work is stopped when a timeout is found.

Complete server.go code:

package main

import (
"context"
pb "demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"log"
"net"
"runtime"
"time"
)

// SimpleService defines our service
type SimpleService struct{<!-- -->}

const (
// Address listening address
Address string = ":8000"
// Network network communication protocol
Network string = "tcp"
)

func main() {<!-- -->
//Listen to the local port
listener, err := net.Listen(Network, Address)
if err != nil {<!-- -->
log.Fatalf("net.Listen err: %v", err)
}
log.Println(Address + " net.Listing...")
//Create a new gRPC server instance
grpcServer := grpc.NewServer()
// Register our service on the gRPC server
pb.RegisterSimpleServer(grpcServer, & amp;SimpleService{<!-- -->})
//Use the server Serve() method and our port information area to implement blocking wait until the process is killed or Stop() is called
err = grpcServer.Serve(listener)
if err != nil {<!-- -->
log.Fatalf("grpcServer.Serve err: %v", err)
}
}

//Route implements the Route method
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {<!-- -->
data := make(chan *pb.SimpleResponse, 1)
go handle(ctx, req, data)
select {<!-- -->
case res := <-data:
log.Println("Return information 1!")
return res, nil
case <-ctx.Done():
log.Println("Return information 2!")
return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
}
}

func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {<!-- -->
select {<!-- -->
case <-ctx.Done():
log.Println("err:", ctx.Err())
//Exit the Go coroutine after timeout
runtime.Goexit()
// Simulate time-consuming operations
case <-time.After(3 * time.Second):
        log.Println("time")
res := pb.SimpleResponse{<!-- -->
Code: 200,
Value: "hello " + req.Data,
}
// Make timeout judgment before modifying the database
        // If it has timed out, exit
// if ctx.Err() == context.Canceled{}
data <- &res
}
}
# Project structure
$ tree demo/
demo/
├── client.go
├── go.mod
├── go.sum
├── prototype
│ └── simple.pb.go
├── server.go
└── simple.proto

1 directory, 6 files

1.4 Operation results

Server:

[root@zsx demo]# go run server.go
2023/02/13 10:22:59 :8000 net.Listing...
2023/02/13 10:23:07 Return information 2!
2023/02/13 10:23:07 err: context deadline exceeded

Client:

[root@zsx demo]# go run client.go
2023/02/13 10:23:07 Route timeout!
exit status 1

1.5 If the client sets a timeout of 5 seconds

//Set 5 seconds timeout
route(ctx, 5)
[root@zsx demo]# go run server.go
2023/02/13 10:24:15 :8000 net.Listing...
2023/02/13 10:24:20 time
2023/02/13 10:24:20 Return information 1!
[root@zsx demo]# go run client.go
2023/02/13 10:24:20 hello grpc

1.6 Summary

The length of the timeout depends on the service itself. For example, returning a hello grpc may only take tens of milliseconds. However, while processing a large amount of data,

Step operations may take a long time. Many factors need to be considered to determine this timeout, such as the end-to-end delay between systems, which RPCs are serial

, which ones can be parallelized, etc.