github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/client/internal/call.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package internal 15 16 import ( 17 "context" 18 "time" 19 20 "github.com/pingcap/tiflow/engine/pkg/rpcutil" 21 "github.com/pingcap/tiflow/pkg/retry" 22 "google.golang.org/grpc" 23 "google.golang.org/grpc/codes" 24 "google.golang.org/grpc/status" 25 ) 26 27 // Call represents a grpc call. 28 type Call[ReqT any, RespT any, F func(context.Context, ReqT, ...grpc.CallOption) (RespT, error)] struct { 29 f F 30 request ReqT 31 opts *callerOpts 32 } 33 34 type callerOpts struct { 35 forceNoRetry bool 36 } 37 38 // CallOption represents an option used to modify the 39 // behavior of Call. 40 type CallOption func(*callerOpts) 41 42 // WithForceNoRetry forbids a call from being retried. 43 // It is typically used if the service provides no idempotency 44 // guarantee at all. 45 func WithForceNoRetry() CallOption { 46 return func(opts *callerOpts) { 47 opts.forceNoRetry = true 48 } 49 } 50 51 // NewCall creates a new Call. 52 func NewCall[F func(context.Context, ReqT, ...grpc.CallOption) (RespT, error), ReqT any, RespT any]( 53 f F, req ReqT, ops ...CallOption, 54 ) *Call[ReqT, RespT, F] { 55 opts := &callerOpts{} 56 57 for _, op := range ops { 58 op(opts) 59 } 60 61 return &Call[ReqT, RespT, F]{ 62 f: f, 63 request: req, 64 opts: opts, 65 } 66 } 67 68 // Do actually performs the Call. 69 func (c *Call[ReqT, RespT, F]) Do( 70 ctx context.Context, 71 ) (RespT, error) { 72 var resp RespT 73 err := retry.Do(ctx, func() error { 74 var err error 75 resp, err = c.f(ctx, c.request) 76 return err 77 }, retry.WithIsRetryableErr(c.isRetryable), 78 retry.WithBackoffBaseDelay(10), 79 retry.WithBackoffMaxDelay(1000), 80 retry.WithTotalRetryDuratoin(10*time.Second)) 81 return resp, rpcutil.FromGRPCError(err) 82 } 83 84 func (c *Call[ReqT, RespT, F]) isRetryable(errIn error) bool { 85 if c.opts.forceNoRetry { 86 return false 87 } 88 89 s, ok := status.FromError(errIn) 90 if !ok { 91 return false 92 } 93 94 return s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded 95 }