Skip to content

Commit b3cd8a3

Browse files
authored
feat: trigger breaker on underlying service timeout (zeromicro#4112)
1 parent a9d27cd commit b3cd8a3

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

zrpc/internal/serverinterceptors/breakerinterceptor.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66

77
"github.com/zeromicro/go-zero/core/breaker"
8+
"github.com/zeromicro/go-zero/core/errorx"
89
"github.com/zeromicro/go-zero/zrpc/internal/codes"
910
"google.golang.org/grpc"
1011
gcodes "google.golang.org/grpc/codes"
@@ -13,11 +14,13 @@ import (
1314

1415
// StreamBreakerInterceptor is an interceptor that acts as a circuit breaker.
1516
func StreamBreakerInterceptor(svr any, stream grpc.ServerStream, info *grpc.StreamServerInfo,
16-
handler grpc.StreamHandler) (err error) {
17+
handler grpc.StreamHandler) error {
1718
breakerName := info.FullMethod
18-
return breaker.DoWithAcceptable(breakerName, func() error {
19+
err := breaker.DoWithAcceptable(breakerName, func() error {
1920
return handler(svr, stream)
20-
}, codes.Acceptable)
21+
}, serverSideAcceptable)
22+
23+
return convertError(err)
2124
}
2225

2326
// UnaryBreakerInterceptor is an interceptor that acts as a circuit breaker.
@@ -28,10 +31,28 @@ func UnaryBreakerInterceptor(ctx context.Context, req any, info *grpc.UnaryServe
2831
var err error
2932
resp, err = handler(ctx, req)
3033
return err
31-
}, codes.Acceptable)
34+
}, serverSideAcceptable)
35+
36+
return resp, convertError(err)
37+
}
38+
39+
func convertError(err error) error {
40+
if err == nil {
41+
return nil
42+
}
43+
44+
// we don't convert context.DeadlineExceeded to status error,
45+
// because grpc will convert it and return to the client.
3246
if errors.Is(err, breaker.ErrServiceUnavailable) {
33-
err = status.Error(gcodes.Unavailable, err.Error())
47+
return status.Error(gcodes.Unavailable, err.Error())
3448
}
3549

36-
return resp, err
50+
return err
51+
}
52+
53+
func serverSideAcceptable(err error) bool {
54+
if errorx.In(err, context.DeadlineExceeded, breaker.ErrServiceUnavailable) {
55+
return false
56+
}
57+
return codes.Acceptable(err)
3758
}

zrpc/internal/serverinterceptors/breakerinterceptor_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,53 @@ func TestUnaryBreakerInterceptor(t *testing.T) {
2929
assert.NotNil(t, err)
3030
}
3131

32+
func TestUnaryBreakerInterceptorOK(t *testing.T) {
33+
_, err := UnaryBreakerInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{
34+
FullMethod: "any",
35+
}, func(_ context.Context, _ any) (any, error) {
36+
return nil, nil
37+
})
38+
assert.NoError(t, err)
39+
}
40+
3241
func TestUnaryBreakerInterceptor_Unavailable(t *testing.T) {
3342
_, err := UnaryBreakerInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{
3443
FullMethod: "any",
3544
}, func(_ context.Context, _ any) (any, error) {
3645
return nil, breaker.ErrServiceUnavailable
3746
})
3847
assert.NotNil(t, err)
48+
code := status.Code(err)
49+
assert.Equal(t, codes.Unavailable, code)
50+
}
51+
52+
func TestUnaryBreakerInterceptor_Deadline(t *testing.T) {
53+
for i := 0; i < 1000; i++ {
54+
_, err := UnaryBreakerInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{
55+
FullMethod: "any",
56+
}, func(_ context.Context, _ any) (any, error) {
57+
return nil, context.DeadlineExceeded
58+
})
59+
switch status.Code(err) {
60+
case codes.Unavailable:
61+
default:
62+
assert.Equal(t, context.DeadlineExceeded, err)
63+
}
64+
}
65+
66+
var dropped bool
67+
for i := 0; i < 100; i++ {
68+
_, err := UnaryBreakerInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{
69+
FullMethod: "any",
70+
}, func(_ context.Context, _ any) (any, error) {
71+
return nil, context.DeadlineExceeded
72+
})
73+
switch status.Code(err) {
74+
case codes.Unavailable:
75+
dropped = true
76+
default:
77+
assert.Equal(t, context.DeadlineExceeded, err)
78+
}
79+
}
80+
assert.True(t, dropped)
3981
}

0 commit comments

Comments
 (0)