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  }