Skip to content

Commit 524ce8f

Browse files
v2: Client unary interceptor timeout on v2 branch (#330)
* Client unary interceptor timeout on v2 branch * change fmt.println with log.Println in example (linter fix) * fix test * Update interceptors/timeout/examples_test.go Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]> * fixup example linter * fix when context already has timeout * fixup trailing period * try increase timing * delete deadline check * update comment * add readme timeout note * fixup test Co-authored-by: Johan Brandhorst-Satzkorn <[email protected]>
1 parent 215af81 commit 524ce8f

File tree

5 files changed

+113
-0
lines changed

5 files changed

+113
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ myServer := grpc.NewServer(
7070

7171
#### Client
7272
* [`retry`](interceptors/retry) - a generic gRPC response code retry mechanism, client-side middleware
73+
* [`timeout`](interceptors/timeout) - a generic gRPC request timeout, client-side middleware
7374

7475
#### Server
7576
* [`validator`](interceptors/validator) - codegen inbound message validation from `.proto` options

interceptors/timeout/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
`grpc_timeout` are interceptors that timeout for gRPC client calls.
3+
4+
Client Side Timeout Middleware
5+
6+
Please see examples for simple examples of use.
7+
*/
8+
package timeout

interceptors/timeout/examples_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package timeout_test
2+
3+
import (
4+
"context"
5+
"log"
6+
"time"
7+
8+
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout"
9+
"github.com/grpc-ecosystem/go-grpc-middleware/v2/testing/testpb"
10+
"google.golang.org/grpc"
11+
)
12+
13+
// Initialization shows an initialization sequence with a custom client request timeout.
14+
func Example_initialization() {
15+
clientConn, err := grpc.Dial(
16+
"ServerAddr",
17+
grpc.WithUnaryInterceptor(
18+
// Set your client request timeout.
19+
timeout.TimeoutUnaryClientInterceptor(20*time.Millisecond),
20+
),
21+
)
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
26+
// Initialize your grpc service with connection.
27+
testServiceClient := testpb.NewTestServiceClient(clientConn)
28+
resp, err := testServiceClient.Ping(context.TODO(), &testpb.PingRequest{Value: "my_example_value"})
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
33+
// Use grpc response value.
34+
log.Println(resp.Value)
35+
}

interceptors/timeout/timeout.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package timeout
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"google.golang.org/grpc"
8+
)
9+
10+
// TimeoutUnaryClientInterceptor returns a new unary client interceptor that sets a timeout on the request context.
11+
func TimeoutUnaryClientInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
12+
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
13+
timedCtx, cancel := context.WithTimeout(ctx, timeout)
14+
defer cancel()
15+
return invoker(timedCtx, method, req, reply, cc, opts...)
16+
}
17+
}

interceptors/timeout/timeout_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package timeout_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout"
9+
"github.com/grpc-ecosystem/go-grpc-middleware/v2/testing/testpb"
10+
"github.com/stretchr/testify/assert"
11+
"google.golang.org/grpc"
12+
)
13+
14+
type TimeoutTestServiceServer struct {
15+
sleepTime time.Duration
16+
testpb.TestPingService
17+
}
18+
19+
func (t *TimeoutTestServiceServer) Ping(ctx context.Context, req *testpb.PingRequest) (*testpb.PingResponse, error) {
20+
if t.sleepTime > 0 {
21+
time.Sleep(t.sleepTime)
22+
}
23+
return t.TestPingService.Ping(ctx, req)
24+
}
25+
26+
func TestTimeoutUnaryClientInterceptor(t *testing.T) {
27+
server := &TimeoutTestServiceServer{}
28+
29+
its := &testpb.InterceptorTestSuite{
30+
ClientOpts: []grpc.DialOption{
31+
grpc.WithUnaryInterceptor(timeout.TimeoutUnaryClientInterceptor(100 * time.Millisecond)),
32+
},
33+
TestService: server,
34+
}
35+
its.Suite.SetT(t)
36+
its.SetupSuite()
37+
defer its.TearDownSuite()
38+
39+
// This call will take 0/100ms for respond, so the client timeout NOT exceed.
40+
resp, err := its.Client.Ping(context.TODO(), &testpb.PingRequest{Value: "default_response_value"})
41+
assert.NoError(t, err)
42+
assert.NotNil(t, resp)
43+
assert.Equal(t, "default_response_value", resp.Value)
44+
45+
// server will sleep 300ms before respond
46+
server.sleepTime = 300 * time.Millisecond
47+
48+
// This call will take 300/100ms for respond, so the client timeout exceed.
49+
resp2, err2 := its.Client.Ping(context.TODO(), &testpb.PingRequest{})
50+
assert.Nil(t, resp2)
51+
assert.EqualError(t, err2, "rpc error: code = DeadlineExceeded desc = context deadline exceeded")
52+
}

0 commit comments

Comments
 (0)