github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/retry/strategy.go (about) 1 // Copyright 2019 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 retry 15 16 import ( 17 "time" 18 19 tcontext "github.com/pingcap/tiflow/dm/pkg/context" 20 "github.com/pingcap/tiflow/dm/pkg/log" 21 "go.uber.org/zap" 22 ) 23 24 // backoffStrategy represents enum of retry wait interval. 25 type backoffStrategy uint8 26 27 const ( 28 // Stable represents fixed time wait retry policy, every retry should wait a fixed time. 29 Stable backoffStrategy = iota + 1 30 // LinearIncrease represents increase time wait retry policy, every retry should wait more time depends on increasing retry times. 31 LinearIncrease 32 ) 33 34 // Params define parameters for Apply 35 // it's a parameters union set of all implements which implement Apply. 36 type Params struct { 37 RetryCount int 38 FirstRetryDuration time.Duration 39 40 BackoffStrategy backoffStrategy 41 42 // IsRetryableFn tells whether we should retry when operateFn failed 43 // params: (number of retry, error of operation) 44 // return: (bool) 45 // 1. true: means operateFn can be retried 46 // 2. false: means operateFn cannot retry after receive this error 47 IsRetryableFn func(int, error) bool 48 } 49 50 func NewParams(retryCount int, firstRetryDuration time.Duration, backoffStrategy backoffStrategy, 51 isRetryableFn func(int, error) bool, 52 ) *Params { 53 return &Params{ 54 RetryCount: retryCount, 55 FirstRetryDuration: firstRetryDuration, 56 BackoffStrategy: backoffStrategy, 57 IsRetryableFn: isRetryableFn, 58 } 59 } 60 61 // OperateFunc the function we can retry 62 // 63 // return: (result of operation, error of operation) 64 type OperateFunc func(*tcontext.Context) (interface{}, error) 65 66 // Strategy define different kind of retry strategy. 67 type Strategy interface { 68 // Apply define retry strategy 69 // params: (retry parameters for this strategy, a normal operation) 70 // return: (result of operation, number of retry, error of operation) 71 Apply(ctx *tcontext.Context, 72 params Params, 73 operateFn OperateFunc, 74 ) (interface{}, int, error) 75 } 76 77 // FiniteRetryStrategy will retry `RetryCount` times when failed to operate DB. 78 type FiniteRetryStrategy struct{} 79 80 // Apply for FiniteRetryStrategy, it waits `FirstRetryDuration` before it starts first retry, and then rest of retries wait time depends on BackoffStrategy. 81 func (*FiniteRetryStrategy) Apply(ctx *tcontext.Context, params Params, operateFn OperateFunc, 82 ) (ret interface{}, i int, err error) { 83 for ; i < params.RetryCount; i++ { 84 ret, err = operateFn(ctx) 85 if err != nil { 86 if params.IsRetryableFn(i, err) { 87 duration := params.FirstRetryDuration 88 89 switch params.BackoffStrategy { 90 case LinearIncrease: 91 duration = time.Duration(i+1) * params.FirstRetryDuration 92 default: 93 } 94 log.L().Warn("retry strategy takes effect", zap.Error(err), zap.Int("retry_times", i), zap.Int("retry_count", params.RetryCount)) 95 96 select { 97 case <-ctx.Context().Done(): 98 return ret, i, err // return `ret` rather than `nil` 99 case <-time.After(duration): 100 } 101 continue 102 } 103 } 104 break 105 } 106 return ret, i, err 107 } 108 109 // Retryer retries operateFn until success or reaches retry limit 110 // todo: merge with Strategy when refactor 111 type Retryer interface { 112 Apply(ctx *tcontext.Context, operateFn OperateFunc) (interface{}, int, error) 113 } 114 115 // FiniteRetryer wraps params. 116 type FiniteRetryer struct { 117 FiniteRetryStrategy 118 Params *Params 119 } 120 121 func (s *FiniteRetryer) Apply(ctx *tcontext.Context, operateFn OperateFunc) (ret interface{}, i int, err error) { 122 return s.FiniteRetryStrategy.Apply(ctx, *s.Params, operateFn) 123 }