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  }