github.com/KinWaiYuen/client-go/v2@v2.5.4/internal/retry/backoff.go (about) 1 // Copyright 2021 TiKV Authors 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // NOTE: The code in this file is based on code from the 16 // TiDB project, licensed under the Apache License v 2.0 17 // 18 // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/retry/backoff.go 19 // 20 21 // Copyright 2016 PingCAP, Inc. 22 // 23 // Licensed under the Apache License, Version 2.0 (the "License"); 24 // you may not use this file except in compliance with the License. 25 // You may obtain a copy of the License at 26 // 27 // http://www.apache.org/licenses/LICENSE-2.0 28 // 29 // Unless required by applicable law or agreed to in writing, software 30 // distributed under the License is distributed on an "AS IS" BASIS, 31 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 // See the License for the specific language governing permissions and 33 // limitations under the License. 34 35 package retry 36 37 import ( 38 "context" 39 "fmt" 40 "math" 41 "strings" 42 "sync/atomic" 43 "time" 44 45 tikverr "github.com/KinWaiYuen/client-go/v2/error" 46 "github.com/KinWaiYuen/client-go/v2/internal/logutil" 47 "github.com/KinWaiYuen/client-go/v2/kv" 48 "github.com/KinWaiYuen/client-go/v2/util" 49 "github.com/opentracing/opentracing-go" 50 "github.com/pingcap/errors" 51 "github.com/pingcap/log" 52 "go.uber.org/zap" 53 "go.uber.org/zap/zapcore" 54 ) 55 56 // Backoffer is a utility for retrying queries. 57 type Backoffer struct { 58 ctx context.Context 59 60 fn map[string]backoffFn 61 maxSleep int 62 totalSleep int 63 excludedSleep int 64 65 vars *kv.Variables 66 noop bool 67 68 errors []error 69 configs []*Config 70 backoffSleepMS map[string]int 71 backoffTimes map[string]int 72 parent *Backoffer 73 } 74 75 type txnStartCtxKeyType struct{} 76 77 // TxnStartKey is a key for transaction start_ts info in context.Context. 78 var TxnStartKey interface{} = txnStartCtxKeyType{} 79 80 // NewBackoffer (Deprecated) creates a Backoffer with maximum sleep time(in ms). 81 func NewBackoffer(ctx context.Context, maxSleep int) *Backoffer { 82 return &Backoffer{ 83 ctx: ctx, 84 maxSleep: maxSleep, 85 vars: kv.DefaultVars, 86 } 87 } 88 89 // NewBackofferWithVars creates a Backoffer with maximum sleep time(in ms) and kv.Variables. 90 func NewBackofferWithVars(ctx context.Context, maxSleep int, vars *kv.Variables) *Backoffer { 91 return NewBackoffer(ctx, maxSleep).withVars(vars) 92 } 93 94 // NewNoopBackoff create a Backoffer do nothing just return error directly 95 func NewNoopBackoff(ctx context.Context) *Backoffer { 96 return &Backoffer{ctx: ctx, noop: true} 97 } 98 99 // withVars sets the kv.Variables to the Backoffer and return it. 100 func (b *Backoffer) withVars(vars *kv.Variables) *Backoffer { 101 if vars != nil { 102 b.vars = vars 103 } 104 // maxSleep is the max sleep time in millisecond. 105 // When it is multiplied by BackOffWeight, it should not be greater than MaxInt32. 106 if b.maxSleep > 0 && math.MaxInt32/b.vars.BackOffWeight >= b.maxSleep { 107 b.maxSleep *= b.vars.BackOffWeight 108 } 109 return b 110 } 111 112 // Backoff sleeps a while base on the Config and records the error message. 113 // It returns a retryable error if total sleep time exceeds maxSleep. 114 func (b *Backoffer) Backoff(cfg *Config, err error) error { 115 if span := opentracing.SpanFromContext(b.ctx); span != nil && span.Tracer() != nil { 116 span1 := span.Tracer().StartSpan(fmt.Sprintf("tikv.backoff.%s", cfg), opentracing.ChildOf(span.Context())) 117 defer span1.Finish() 118 opentracing.ContextWithSpan(b.ctx, span1) 119 } 120 return b.BackoffWithCfgAndMaxSleep(cfg, -1, err) 121 } 122 123 // BackoffWithMaxSleepTxnLockFast sleeps a while base on the MaxSleepTxnLock and records the error message 124 // and never sleep more than maxSleepMs for each sleep. 125 func (b *Backoffer) BackoffWithMaxSleepTxnLockFast(maxSleepMs int, err error) error { 126 cfg := BoTxnLockFast 127 return b.BackoffWithCfgAndMaxSleep(cfg, maxSleepMs, err) 128 } 129 130 // BackoffWithCfgAndMaxSleep sleeps a while base on the Config and records the error message 131 // and never sleep more than maxSleepMs for each sleep. 132 func (b *Backoffer) BackoffWithCfgAndMaxSleep(cfg *Config, maxSleepMs int, err error) error { 133 if strings.Contains(err.Error(), tikverr.MismatchClusterID) { 134 logutil.BgLogger().Fatal("critical error", zap.Error(err)) 135 } 136 select { 137 case <-b.ctx.Done(): 138 return errors.Trace(err) 139 default: 140 } 141 142 b.errors = append(b.errors, errors.Errorf("%s at %s", err.Error(), time.Now().Format(time.RFC3339Nano))) 143 b.configs = append(b.configs, cfg) 144 if b.noop || (b.maxSleep > 0 && (b.totalSleep-b.excludedSleep) >= b.maxSleep) { 145 errMsg := fmt.Sprintf("%s backoffer.maxSleep %dms is exceeded, errors:", cfg.String(), b.maxSleep) 146 for i, err := range b.errors { 147 // Print only last 3 errors for non-DEBUG log levels. 148 if log.GetLevel() == zapcore.DebugLevel || i >= len(b.errors)-3 { 149 errMsg += "\n" + err.Error() 150 } 151 } 152 logutil.BgLogger().Warn(errMsg) 153 // Use the first backoff type to generate a MySQL error. 154 return b.configs[0].err 155 } 156 157 // Lazy initialize. 158 if b.fn == nil { 159 b.fn = make(map[string]backoffFn) 160 } 161 f, ok := b.fn[cfg.name] 162 if !ok { 163 f = cfg.createBackoffFn(b.vars) 164 b.fn[cfg.name] = f 165 } 166 realSleep := f(b.ctx, maxSleepMs) 167 if cfg.metric != nil { 168 (*cfg.metric).Observe(float64(realSleep) / 1000) 169 } 170 171 b.totalSleep += realSleep 172 if _, ok := isSleepExcluded[cfg]; ok { 173 b.excludedSleep += realSleep 174 } 175 if b.backoffSleepMS == nil { 176 b.backoffSleepMS = make(map[string]int) 177 } 178 b.backoffSleepMS[cfg.name] += realSleep 179 if b.backoffTimes == nil { 180 b.backoffTimes = make(map[string]int) 181 } 182 b.backoffTimes[cfg.name]++ 183 184 stmtExec := b.ctx.Value(util.ExecDetailsKey) 185 if stmtExec != nil { 186 detail := stmtExec.(*util.ExecDetails) 187 atomic.AddInt64(&detail.BackoffDuration, int64(realSleep)*int64(time.Millisecond)) 188 atomic.AddInt64(&detail.BackoffCount, 1) 189 } 190 191 if b.vars != nil && b.vars.Killed != nil { 192 if atomic.LoadUint32(b.vars.Killed) == 1 { 193 return tikverr.ErrQueryInterrupted 194 } 195 } 196 197 var startTs interface{} 198 if ts := b.ctx.Value(TxnStartKey); ts != nil { 199 startTs = ts 200 } 201 logutil.Logger(b.ctx).Debug("retry later", 202 zap.Error(err), 203 zap.Int("totalSleep", b.totalSleep), 204 zap.Int("excludedSleep", b.excludedSleep), 205 zap.Int("maxSleep", b.maxSleep), 206 zap.Stringer("type", cfg), 207 zap.Reflect("txnStartTS", startTs)) 208 return nil 209 } 210 211 func (b *Backoffer) String() string { 212 if b.totalSleep == 0 { 213 return "" 214 } 215 return fmt.Sprintf(" backoff(%dms %v)", b.totalSleep, b.configs) 216 } 217 218 // Clone creates a new Backoffer which keeps current Backoffer's sleep time and errors, and shares 219 // current Backoffer's context. 220 func (b *Backoffer) Clone() *Backoffer { 221 return &Backoffer{ 222 ctx: b.ctx, 223 maxSleep: b.maxSleep, 224 totalSleep: b.totalSleep, 225 excludedSleep: b.excludedSleep, 226 errors: b.errors, 227 vars: b.vars, 228 parent: b.parent, 229 } 230 } 231 232 // Fork creates a new Backoffer which keeps current Backoffer's sleep time and errors, and holds 233 // a child context of current Backoffer's context. 234 func (b *Backoffer) Fork() (*Backoffer, context.CancelFunc) { 235 ctx, cancel := context.WithCancel(b.ctx) 236 return &Backoffer{ 237 ctx: ctx, 238 maxSleep: b.maxSleep, 239 totalSleep: b.totalSleep, 240 excludedSleep: b.excludedSleep, 241 errors: b.errors, 242 vars: b.vars, 243 parent: b, 244 }, cancel 245 } 246 247 // GetVars returns the binded vars. 248 func (b *Backoffer) GetVars() *kv.Variables { 249 return b.vars 250 } 251 252 // GetTotalSleep returns total sleep time. 253 func (b *Backoffer) GetTotalSleep() int { 254 return b.totalSleep 255 } 256 257 // GetTypes returns type list of this backoff and all its ancestors. 258 func (b *Backoffer) GetTypes() []string { 259 typs := make([]string, 0, len(b.configs)) 260 for b != nil { 261 for _, cfg := range b.configs { 262 typs = append(typs, cfg.String()) 263 } 264 b = b.parent 265 } 266 return typs 267 } 268 269 // GetCtx returns the binded context. 270 func (b *Backoffer) GetCtx() context.Context { 271 return b.ctx 272 } 273 274 // SetCtx sets the binded context to ctx. 275 func (b *Backoffer) SetCtx(ctx context.Context) { 276 b.ctx = ctx 277 } 278 279 // GetBackoffTimes returns a map contains backoff time count by type. 280 func (b *Backoffer) GetBackoffTimes() map[string]int { 281 return b.backoffTimes 282 } 283 284 // GetTotalBackoffTimes returns the total backoff times of the backoffer. 285 func (b *Backoffer) GetTotalBackoffTimes() int { 286 total := 0 287 for _, time := range b.backoffTimes { 288 total += time 289 } 290 return total 291 } 292 293 // GetBackoffSleepMS returns a map contains backoff sleep time by type. 294 func (b *Backoffer) GetBackoffSleepMS() map[string]int { 295 return b.backoffSleepMS 296 } 297 298 // ErrorsNum returns the number of errors. 299 func (b *Backoffer) ErrorsNum() int { 300 return len(b.errors) 301 } 302 303 // Reset resets the sleep state of the backoffer, so that following backoff 304 // can sleep shorter. The reason why we don't create a new backoffer is that 305 // backoffer is similar to context and it records some metrics that we 306 // want to record for an entire process which is composed of serveral stages. 307 func (b *Backoffer) Reset() { 308 b.fn = nil 309 b.totalSleep = 0 310 b.excludedSleep = 0 311 } 312 313 // ResetMaxSleep resets the sleep state and max sleep limit of the backoffer. 314 // It's used when switches to the next stage of the process. 315 func (b *Backoffer) ResetMaxSleep(maxSleep int) { 316 b.Reset() 317 b.maxSleep = maxSleep 318 b.withVars(b.vars) 319 }