github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/milevadb-server/einsteindb/lock_resolver.go (about)

     1  // Copyright 2020 WHTCORPS INC, 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 einsteindb
    15  
    16  import (
    17  	"bytes"
    18  	"container/list"
    19  	"context"
    20  	"fmt"
    21  	"math"
    22  	"sync"
    23  	"time"
    24  
    25  	fidel "github.com/einsteindb/fidel/client"
    26  	"github.com/whtcorpsinc/ekvproto/pkg/ekvrpcpb"
    27  	"github.com/whtcorpsinc/errors"
    28  	"github.com/whtcorpsinc/failpoint"
    29  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc"
    30  	"github.com/whtcorpsinc/milevadb/config"
    31  	"github.com/whtcorpsinc/milevadb/ekv"
    32  	"github.com/whtcorpsinc/milevadb/metrics"
    33  	"github.com/whtcorpsinc/milevadb/soliton/execdetails"
    34  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    35  	"go.uber.org/zap"
    36  )
    37  
    38  // ResolvedCacheSize is max number of cached txn status.
    39  const ResolvedCacheSize = 2048
    40  
    41  // bigTxnThreshold : transaction involves keys exceed this threshold can be treated as `big transaction`.
    42  const bigTxnThreshold = 16
    43  
    44  var (
    45  	einsteindbLockResolverCountWithBatchResolve             = metrics.EinsteinDBLockResolverCounter.WithLabelValues("batch_resolve")
    46  	einsteindbLockResolverCountWithExpired                  = metrics.EinsteinDBLockResolverCounter.WithLabelValues("expired")
    47  	einsteindbLockResolverCountWithNotExpired               = metrics.EinsteinDBLockResolverCounter.WithLabelValues("not_expired")
    48  	einsteindbLockResolverCountWithWaitExpired              = metrics.EinsteinDBLockResolverCounter.WithLabelValues("wait_expired")
    49  	einsteindbLockResolverCountWithResolve                  = metrics.EinsteinDBLockResolverCounter.WithLabelValues("resolve")
    50  	einsteindbLockResolverCountWithResolveForWrite          = metrics.EinsteinDBLockResolverCounter.WithLabelValues("resolve_for_write")
    51  	einsteindbLockResolverCountWithResolveAsync             = metrics.EinsteinDBLockResolverCounter.WithLabelValues("resolve_async_commit")
    52  	einsteindbLockResolverCountWithWriteConflict            = metrics.EinsteinDBLockResolverCounter.WithLabelValues("write_conflict")
    53  	einsteindbLockResolverCountWithQueryTxnStatus           = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_txn_status")
    54  	einsteindbLockResolverCountWithQueryTxnStatusCommitted  = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_txn_status_committed")
    55  	einsteindbLockResolverCountWithQueryTxnStatusRolledBack = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_txn_status_rolled_back")
    56  	einsteindbLockResolverCountWithQueryCheckSecondaryLocks = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_check_secondary_locks")
    57  	einsteindbLockResolverCountWithResolveLocks             = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_resolve_locks")
    58  	einsteindbLockResolverCountWithResolveLockLite          = metrics.EinsteinDBLockResolverCounter.WithLabelValues("query_resolve_lock_lite")
    59  )
    60  
    61  // LockResolver resolves locks and also caches resolved txn status.
    62  type LockResolver struct {
    63  	causetstore CausetStorage
    64  	mu          struct {
    65  		sync.RWMutex
    66  		// resolved caches resolved txns (FIFO, txn id -> txnStatus).
    67  		resolved       map[uint64]TxnStatus
    68  		recentResolved *list.List
    69  	}
    70  	testingKnobs struct {
    71  		meetLock func(locks []*Lock)
    72  	}
    73  }
    74  
    75  func newLockResolver(causetstore CausetStorage) *LockResolver {
    76  	r := &LockResolver{
    77  		causetstore: causetstore,
    78  	}
    79  	r.mu.resolved = make(map[uint64]TxnStatus)
    80  	r.mu.recentResolved = list.New()
    81  	return r
    82  }
    83  
    84  // NewLockResolver is exported for other pkg to use, suppress unused warning.
    85  var _ = NewLockResolver
    86  
    87  // NewLockResolver creates a LockResolver.
    88  // It is exported for other pkg to use. For instance, binlog service needs
    89  // to determine a transaction's commit state.
    90  func NewLockResolver(etcdAddrs []string, security config.Security, opts ...fidel.ClientOption) (*LockResolver, error) {
    91  	FIDelCli, err := fidel.NewClient(etcdAddrs, fidel.SecurityOption{
    92  		CAPath:   security.ClusterSSLCA,
    93  		CertPath: security.ClusterSSLCert,
    94  		KeyPath:  security.ClusterSSLKey,
    95  	}, opts...)
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	FIDelCli = execdetails.InterceptedFIDelClient{Client: FIDelCli}
   100  	uuid := fmt.Sprintf("einsteindb-%v", FIDelCli.GetClusterID(context.TODO()))
   101  
   102  	tlsConfig, err := security.ToTLSConfig()
   103  	if err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  
   107  	spekv, err := NewEtcdSafePointKV(etcdAddrs, tlsConfig)
   108  	if err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  
   112  	s, err := newEinsteinDBStore(uuid, &codecFIDelClient{FIDelCli}, spekv, newRPCClient(security), false, nil)
   113  	if err != nil {
   114  		return nil, errors.Trace(err)
   115  	}
   116  	return s.lockResolver, nil
   117  }
   118  
   119  // TxnStatus represents a txn's final status. It should be Lock or Commit or Rollback.
   120  type TxnStatus struct {
   121  	ttl         uint64
   122  	commitTS    uint64
   123  	action      ekvrpcpb.CausetAction
   124  	primaryLock *ekvrpcpb.LockInfo
   125  }
   126  
   127  // IsCommitted returns true if the txn's final status is Commit.
   128  func (s TxnStatus) IsCommitted() bool { return s.ttl == 0 && s.commitTS > 0 }
   129  
   130  // CommitTS returns the txn's commitTS. It is valid iff `IsCommitted` is true.
   131  func (s TxnStatus) CommitTS() uint64 { return s.commitTS }
   132  
   133  // TTL returns the TTL of the transaction if the transaction is still alive.
   134  func (s TxnStatus) TTL() uint64 { return s.ttl }
   135  
   136  // CausetAction returns what the CheckTxnStatus request have done to the transaction.
   137  func (s TxnStatus) CausetAction() ekvrpcpb.CausetAction { return s.action }
   138  
   139  // By default, locks after 3000ms is considered unusual (the client created the
   140  // dagger might be dead). Other client may cleanup this HoTT of dagger.
   141  // For locks created recently, we will do backoff and retry.
   142  var defaultLockTTL uint64 = 3000
   143  
   144  // ttl = ttlFactor * sqrt(writeSizeInMiB)
   145  var ttlFactor = 6000
   146  
   147  // Lock represents a dagger from einsteindb server.
   148  type Lock struct {
   149  	Key                []byte
   150  	Primary            []byte
   151  	TxnID              uint64
   152  	TTL                uint64
   153  	TxnSize            uint64
   154  	LockType           ekvrpcpb.Op
   155  	UseAsyncCommit     bool
   156  	LockForUFIDelateTS uint64
   157  	MinCommitTS        uint64
   158  }
   159  
   160  func (l *Lock) String() string {
   161  	buf := bytes.NewBuffer(make([]byte, 0, 128))
   162  	buf.WriteString("key: ")
   163  	prettyWriteKey(buf, l.Key)
   164  	buf.WriteString(", primary: ")
   165  	prettyWriteKey(buf, l.Primary)
   166  	return fmt.Sprintf("%s, txnStartTS: %d, lockForUFIDelateTS:%d, minCommitTs:%d, ttl: %d, type: %s", buf.String(), l.TxnID, l.LockForUFIDelateTS, l.MinCommitTS, l.TTL, l.LockType)
   167  }
   168  
   169  // NewLock creates a new *Lock.
   170  func NewLock(l *ekvrpcpb.LockInfo) *Lock {
   171  	return &Lock{
   172  		Key:                l.GetKey(),
   173  		Primary:            l.GetPrimaryLock(),
   174  		TxnID:              l.GetLockVersion(),
   175  		TTL:                l.GetLockTtl(),
   176  		TxnSize:            l.GetTxnSize(),
   177  		LockType:           l.LockType,
   178  		UseAsyncCommit:     l.UseAsyncCommit,
   179  		LockForUFIDelateTS: l.LockForUFIDelateTs,
   180  		MinCommitTS:        l.MinCommitTs,
   181  	}
   182  }
   183  
   184  func (lr *LockResolver) saveResolved(txnID uint64, status TxnStatus) {
   185  	lr.mu.Lock()
   186  	defer lr.mu.Unlock()
   187  
   188  	if _, ok := lr.mu.resolved[txnID]; ok {
   189  		return
   190  	}
   191  	lr.mu.resolved[txnID] = status
   192  	lr.mu.recentResolved.PushBack(txnID)
   193  	if len(lr.mu.resolved) > ResolvedCacheSize {
   194  		front := lr.mu.recentResolved.Front()
   195  		delete(lr.mu.resolved, front.Value.(uint64))
   196  		lr.mu.recentResolved.Remove(front)
   197  	}
   198  }
   199  
   200  func (lr *LockResolver) getResolved(txnID uint64) (TxnStatus, bool) {
   201  	lr.mu.RLock()
   202  	defer lr.mu.RUnlock()
   203  
   204  	s, ok := lr.mu.resolved[txnID]
   205  	return s, ok
   206  }
   207  
   208  // BatchResolveLocks resolve locks in a batch.
   209  // Used it in gcworker only!
   210  func (lr *LockResolver) BatchResolveLocks(bo *Backoffer, locks []*Lock, loc RegionVerID) (bool, error) {
   211  	if len(locks) == 0 {
   212  		return true, nil
   213  	}
   214  
   215  	einsteindbLockResolverCountWithBatchResolve.Inc()
   216  
   217  	// The GCWorker kill all ongoing transactions, because it must make sure all
   218  	// locks have been cleaned before GC.
   219  	expiredLocks := locks
   220  
   221  	callerStartTS, err := lr.causetstore.GetOracle().GetTimestamp(bo.ctx)
   222  	if err != nil {
   223  		return false, errors.Trace(err)
   224  	}
   225  
   226  	txnInfos := make(map[uint64]uint64)
   227  	startTime := time.Now()
   228  	for _, l := range expiredLocks {
   229  		if _, ok := txnInfos[l.TxnID]; ok {
   230  			continue
   231  		}
   232  		einsteindbLockResolverCountWithExpired.Inc()
   233  
   234  		// Use currentTS = math.MaxUint64 means rollback the txn, no matter the dagger is expired or not!
   235  		status, err := lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, math.MaxUint64, true)
   236  		if err != nil {
   237  			return false, err
   238  		}
   239  
   240  		// If the transaction uses async commit, CheckTxnStatus will reject rolling back the primary dagger.
   241  		// Then we need to check the secondary locks to determine the final status of the transaction.
   242  		if status.primaryLock != nil && status.primaryLock.UseAsyncCommit {
   243  			resolveData, err := lr.checkAllSecondaries(bo, l, &status)
   244  			if err != nil {
   245  				return false, err
   246  			}
   247  			txnInfos[l.TxnID] = resolveData.commitTs
   248  			continue
   249  		}
   250  
   251  		if status.ttl > 0 {
   252  			logutil.BgLogger().Error("BatchResolveLocks fail to clean locks, this result is not expected!")
   253  			return false, errors.New("MilevaDB ask EinsteinDB to rollback locks but it doesn't, the protocol maybe wrong")
   254  		}
   255  
   256  		txnInfos[l.TxnID] = status.commitTS
   257  	}
   258  	logutil.BgLogger().Info("BatchResolveLocks: lookup txn status",
   259  		zap.Duration("cost time", time.Since(startTime)),
   260  		zap.Int("num of txn", len(txnInfos)))
   261  
   262  	listTxnInfos := make([]*ekvrpcpb.TxnInfo, 0, len(txnInfos))
   263  	for txnID, status := range txnInfos {
   264  		listTxnInfos = append(listTxnInfos, &ekvrpcpb.TxnInfo{
   265  			Txn:    txnID,
   266  			Status: status,
   267  		})
   268  	}
   269  
   270  	req := einsteindbrpc.NewRequest(einsteindbrpc.CmdResolveLock, &ekvrpcpb.ResolveLockRequest{TxnInfos: listTxnInfos})
   271  	startTime = time.Now()
   272  	resp, err := lr.causetstore.SendReq(bo, req, loc, readTimeoutShort)
   273  	if err != nil {
   274  		return false, errors.Trace(err)
   275  	}
   276  
   277  	regionErr, err := resp.GetRegionError()
   278  	if err != nil {
   279  		return false, errors.Trace(err)
   280  	}
   281  
   282  	if regionErr != nil {
   283  		err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
   284  		if err != nil {
   285  			return false, errors.Trace(err)
   286  		}
   287  		return false, nil
   288  	}
   289  
   290  	if resp.Resp == nil {
   291  		return false, errors.Trace(ErrBodyMissing)
   292  	}
   293  	cmdResp := resp.Resp.(*ekvrpcpb.ResolveLockResponse)
   294  	if keyErr := cmdResp.GetError(); keyErr != nil {
   295  		return false, errors.Errorf("unexpected resolve err: %s", keyErr)
   296  	}
   297  
   298  	logutil.BgLogger().Info("BatchResolveLocks: resolve locks in a batch",
   299  		zap.Duration("cost time", time.Since(startTime)),
   300  		zap.Int("num of locks", len(expiredLocks)))
   301  	return true, nil
   302  }
   303  
   304  // ResolveLocks tries to resolve Locks. The resolving process is in 3 steps:
   305  // 1) Use the `lockTTL` to pick up all expired locks. Only locks that are too
   306  //    old are considered orphan locks and will be handled later. If all locks
   307  //    are expired then all locks will be resolved so the returned `ok` will be
   308  //    true, otherwise caller should sleep a while before retry.
   309  // 2) For each dagger, query the primary key to get txn(which left the dagger)'s
   310  //    commit status.
   311  // 3) Send `ResolveLock` cmd to the dagger's region to resolve all locks belong to
   312  //    the same transaction.
   313  func (lr *LockResolver) ResolveLocks(bo *Backoffer, callerStartTS uint64, locks []*Lock) (int64, []uint64 /*pushed*/, error) {
   314  	return lr.resolveLocks(bo, callerStartTS, locks, false, false)
   315  }
   316  
   317  func (lr *LockResolver) resolveLocksLite(bo *Backoffer, callerStartTS uint64, locks []*Lock) (int64, []uint64 /*pushed*/, error) {
   318  	return lr.resolveLocks(bo, callerStartTS, locks, false, true)
   319  }
   320  
   321  func (lr *LockResolver) resolveLocks(bo *Backoffer, callerStartTS uint64, locks []*Lock, forWrite bool, lite bool) (int64, []uint64 /*pushed*/, error) {
   322  	if lr.testingKnobs.meetLock != nil {
   323  		lr.testingKnobs.meetLock(locks)
   324  	}
   325  	var msBeforeTxnExpired txnExpireTime
   326  	if len(locks) == 0 {
   327  		return msBeforeTxnExpired.value(), nil, nil
   328  	}
   329  
   330  	if forWrite {
   331  		einsteindbLockResolverCountWithResolveForWrite.Inc()
   332  	} else {
   333  		einsteindbLockResolverCountWithResolve.Inc()
   334  	}
   335  
   336  	var pushFail bool
   337  	// TxnID -> []Region, record resolved Regions.
   338  	// TODO: Maybe put it in LockResolver and share by all txns.
   339  	cleanTxns := make(map[uint64]map[RegionVerID]struct{})
   340  	var pushed []uint64
   341  	// pushed is only used in the read operation.
   342  	if !forWrite {
   343  		pushed = make([]uint64, 0, len(locks))
   344  	}
   345  
   346  	for _, l := range locks {
   347  		status, err := lr.getTxnStatusFromLock(bo, l, callerStartTS)
   348  		if err != nil {
   349  			msBeforeTxnExpired.uFIDelate(0)
   350  			err = errors.Trace(err)
   351  			return msBeforeTxnExpired.value(), nil, err
   352  		}
   353  
   354  		if status.ttl == 0 {
   355  			einsteindbLockResolverCountWithExpired.Inc()
   356  			// If the dagger is committed or rollbacked, resolve dagger.
   357  			cleanRegions, exists := cleanTxns[l.TxnID]
   358  			if !exists {
   359  				cleanRegions = make(map[RegionVerID]struct{})
   360  				cleanTxns[l.TxnID] = cleanRegions
   361  			}
   362  
   363  			if status.primaryLock != nil && status.primaryLock.UseAsyncCommit && !exists {
   364  				err = lr.resolveLockAsync(bo, l, status)
   365  			} else if l.LockType == ekvrpcpb.Op_PessimisticLock {
   366  				err = lr.resolvePessimisticLock(bo, l, cleanRegions)
   367  			} else {
   368  				err = lr.resolveLock(bo, l, status, lite, cleanRegions)
   369  			}
   370  			if err != nil {
   371  				msBeforeTxnExpired.uFIDelate(0)
   372  				err = errors.Trace(err)
   373  				return msBeforeTxnExpired.value(), nil, err
   374  			}
   375  		} else {
   376  			einsteindbLockResolverCountWithNotExpired.Inc()
   377  			// If the dagger is valid, the txn may be a pessimistic transaction.
   378  			// UFIDelate the txn expire time.
   379  			msBeforeLockExpired := lr.causetstore.GetOracle().UntilExpired(l.TxnID, status.ttl)
   380  			msBeforeTxnExpired.uFIDelate(msBeforeLockExpired)
   381  			if forWrite {
   382  				// Write conflict detected!
   383  				// If it's a optimistic conflict and current txn is earlier than the dagger tenant,
   384  				// abort current transaction.
   385  				// This could avoids the deadlock scene of two large transaction.
   386  				if l.LockType != ekvrpcpb.Op_PessimisticLock && l.TxnID > callerStartTS {
   387  					einsteindbLockResolverCountWithWriteConflict.Inc()
   388  					return msBeforeTxnExpired.value(), nil, ekv.ErrWriteConflict.GenWithStackByArgs(callerStartTS, l.TxnID, status.commitTS, l.Key)
   389  				}
   390  			} else {
   391  				if status.action != ekvrpcpb.CausetAction_MinCommitTSPushed {
   392  					pushFail = true
   393  					continue
   394  				}
   395  				pushed = append(pushed, l.TxnID)
   396  			}
   397  		}
   398  	}
   399  	if pushFail {
   400  		// If any of the dagger fails to push minCommitTS, don't return the pushed array.
   401  		pushed = nil
   402  	}
   403  
   404  	if msBeforeTxnExpired.value() > 0 && len(pushed) == 0 {
   405  		// If len(pushed) > 0, the caller will not causet on the locks, it push the minCommitTS instead.
   406  		einsteindbLockResolverCountWithWaitExpired.Inc()
   407  	}
   408  	return msBeforeTxnExpired.value(), pushed, nil
   409  }
   410  
   411  func (lr *LockResolver) resolveLocksForWrite(bo *Backoffer, callerStartTS uint64, locks []*Lock) (int64, error) {
   412  	msBeforeTxnExpired, _, err := lr.resolveLocks(bo, callerStartTS, locks, true, false)
   413  	return msBeforeTxnExpired, err
   414  }
   415  
   416  type txnExpireTime struct {
   417  	initialized bool
   418  	txnExpire   int64
   419  }
   420  
   421  func (t *txnExpireTime) uFIDelate(lockExpire int64) {
   422  	if lockExpire <= 0 {
   423  		lockExpire = 0
   424  	}
   425  	if !t.initialized {
   426  		t.txnExpire = lockExpire
   427  		t.initialized = true
   428  		return
   429  	}
   430  	if lockExpire < t.txnExpire {
   431  		t.txnExpire = lockExpire
   432  	}
   433  }
   434  
   435  func (t *txnExpireTime) value() int64 {
   436  	if !t.initialized {
   437  		return 0
   438  	}
   439  	return t.txnExpire
   440  }
   441  
   442  // GetTxnStatus queries einsteindb-server for a txn's status (commit/rollback).
   443  // If the primary key is still locked, it will launch a Rollback to abort it.
   444  // To avoid unnecessarily aborting too many txns, it is wiser to wait a few
   445  // seconds before calling it after Prewrite.
   446  func (lr *LockResolver) GetTxnStatus(txnID uint64, callerStartTS uint64, primary []byte) (TxnStatus, error) {
   447  	var status TxnStatus
   448  	bo := NewBackoffer(context.Background(), cleanupMaxBackoff)
   449  	currentTS, err := lr.causetstore.GetOracle().GetLowResolutionTimestamp(bo.ctx)
   450  	if err != nil {
   451  		return status, err
   452  	}
   453  	return lr.getTxnStatus(bo, txnID, primary, callerStartTS, currentTS, true)
   454  }
   455  
   456  func (lr *LockResolver) getTxnStatusFromLock(bo *Backoffer, l *Lock, callerStartTS uint64) (TxnStatus, error) {
   457  	var currentTS uint64
   458  	var err error
   459  	var status TxnStatus
   460  	if l.UseAsyncCommit {
   461  		// Async commit doesn't need the current ts since it uses the minCommitTS.
   462  		currentTS = 0
   463  		// Set to 0 so as not to push forward min commit ts.
   464  		callerStartTS = 0
   465  	} else if l.TTL == 0 {
   466  		// NOTE: l.TTL = 0 is a special protocol!!!
   467  		// When the pessimistic txn prewrite meets locks of a txn, it should resolve the dagger **unconditionally**.
   468  		// In this case, EinsteinDB use dagger TTL = 0 to notify MilevaDB, and MilevaDB should resolve the dagger!
   469  		// Set currentTS to max uint64 to make the dagger expired.
   470  		currentTS = math.MaxUint64
   471  	} else {
   472  		currentTS, err = lr.causetstore.GetOracle().GetLowResolutionTimestamp(bo.ctx)
   473  		if err != nil {
   474  			return TxnStatus{}, err
   475  		}
   476  	}
   477  
   478  	rollbackIfNotExist := false
   479  	failpoint.Inject("getTxnStatusDelay", func() {
   480  		time.Sleep(100 * time.Millisecond)
   481  	})
   482  	for {
   483  		status, err = lr.getTxnStatus(bo, l.TxnID, l.Primary, callerStartTS, currentTS, rollbackIfNotExist)
   484  		if err == nil {
   485  			return status, nil
   486  		}
   487  		// If the error is something other than txnNotFoundErr, throw the error (network
   488  		// unavailable, einsteindb down, backoff timeout etc) to the caller.
   489  		if _, ok := errors.Cause(err).(txnNotFoundErr); !ok {
   490  			return TxnStatus{}, err
   491  		}
   492  
   493  		failpoint.Inject("txnNotFoundRetTTL", func() {
   494  			failpoint.Return(TxnStatus{ttl: l.TTL, action: ekvrpcpb.CausetAction_NoCausetAction}, nil)
   495  		})
   496  
   497  		// Handle txnNotFound error.
   498  		// getTxnStatus() returns it when the secondary locks exist while the primary dagger doesn't.
   499  		// This is likely to happen in the concurrently prewrite when secondary regions
   500  		// success before the primary region.
   501  		if err := bo.Backoff(boTxnNotFound, err); err != nil {
   502  			logutil.Logger(bo.ctx).Warn("getTxnStatusFromLock backoff fail", zap.Error(err))
   503  		}
   504  
   505  		if lr.causetstore.GetOracle().UntilExpired(l.TxnID, l.TTL) <= 0 {
   506  			logutil.Logger(bo.ctx).Warn("dagger txn not found, dagger has expired",
   507  				zap.Uint64("CallerStartTs", callerStartTS),
   508  				zap.Stringer("dagger str", l))
   509  			if l.LockType == ekvrpcpb.Op_PessimisticLock {
   510  				failpoint.Inject("txnExpireRetTTL", func() {
   511  					failpoint.Return(TxnStatus{ttl: l.TTL, action: ekvrpcpb.CausetAction_NoCausetAction},
   512  						errors.New("error txn not found and dagger expired"))
   513  				})
   514  				return TxnStatus{}, nil
   515  			}
   516  			rollbackIfNotExist = true
   517  		} else {
   518  			if l.LockType == ekvrpcpb.Op_PessimisticLock {
   519  				return TxnStatus{ttl: l.TTL}, nil
   520  			}
   521  		}
   522  	}
   523  }
   524  
   525  type txnNotFoundErr struct {
   526  	*ekvrpcpb.TxnNotFound
   527  }
   528  
   529  func (e txnNotFoundErr) Error() string {
   530  	return e.TxnNotFound.String()
   531  }
   532  
   533  // getTxnStatus sends the CheckTxnStatus request to the EinsteinDB server.
   534  // When rollbackIfNotExist is false, the caller should be careful with the txnNotFoundErr error.
   535  func (lr *LockResolver) getTxnStatus(bo *Backoffer, txnID uint64, primary []byte, callerStartTS, currentTS uint64, rollbackIfNotExist bool) (TxnStatus, error) {
   536  	if s, ok := lr.getResolved(txnID); ok {
   537  		return s, nil
   538  	}
   539  
   540  	einsteindbLockResolverCountWithQueryTxnStatus.Inc()
   541  
   542  	// CheckTxnStatus may meet the following cases:
   543  	// 1. LOCK
   544  	// 1.1 Lock expired -- orphan dagger, fail to uFIDelate TTL, crash recovery etc.
   545  	// 1.2 Lock TTL -- active transaction holding the dagger.
   546  	// 2. NO LOCK
   547  	// 2.1 Txn Committed
   548  	// 2.2 Txn Rollbacked -- rollback itself, rollback by others, GC tomb etc.
   549  	// 2.3 No dagger -- pessimistic dagger rollback, concurrence prewrite.
   550  
   551  	var status TxnStatus
   552  	req := einsteindbrpc.NewRequest(einsteindbrpc.CmdCheckTxnStatus, &ekvrpcpb.CheckTxnStatusRequest{
   553  		PrimaryKey:         primary,
   554  		LockTs:             txnID,
   555  		CallerStartTs:      callerStartTS,
   556  		CurrentTs:          currentTS,
   557  		RollbackIfNotExist: rollbackIfNotExist,
   558  	})
   559  	for {
   560  		loc, err := lr.causetstore.GetRegionCache().LocateKey(bo, primary)
   561  		if err != nil {
   562  			return status, errors.Trace(err)
   563  		}
   564  		resp, err := lr.causetstore.SendReq(bo, req, loc.Region, readTimeoutShort)
   565  		if err != nil {
   566  			return status, errors.Trace(err)
   567  		}
   568  		regionErr, err := resp.GetRegionError()
   569  		if err != nil {
   570  			return status, errors.Trace(err)
   571  		}
   572  		if regionErr != nil {
   573  			err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
   574  			if err != nil {
   575  				return status, errors.Trace(err)
   576  			}
   577  			continue
   578  		}
   579  		if resp.Resp == nil {
   580  			return status, errors.Trace(ErrBodyMissing)
   581  		}
   582  		cmdResp := resp.Resp.(*ekvrpcpb.CheckTxnStatusResponse)
   583  		if keyErr := cmdResp.GetError(); keyErr != nil {
   584  			txnNotFound := keyErr.GetTxnNotFound()
   585  			if txnNotFound != nil {
   586  				return status, txnNotFoundErr{txnNotFound}
   587  			}
   588  
   589  			err = errors.Errorf("unexpected err: %s, tid: %v", keyErr, txnID)
   590  			logutil.BgLogger().Error("getTxnStatus error", zap.Error(err))
   591  			return status, err
   592  		}
   593  		status.action = cmdResp.CausetAction
   594  		status.primaryLock = cmdResp.LockInfo
   595  
   596  		if status.primaryLock != nil && status.primaryLock.UseAsyncCommit {
   597  			if !lr.causetstore.GetOracle().IsExpired(txnID, cmdResp.LockTtl) {
   598  				status.ttl = cmdResp.LockTtl
   599  			}
   600  		} else if cmdResp.LockTtl != 0 {
   601  			status.ttl = cmdResp.LockTtl
   602  		} else {
   603  			if cmdResp.CommitVersion == 0 {
   604  				einsteindbLockResolverCountWithQueryTxnStatusRolledBack.Inc()
   605  			} else {
   606  				einsteindbLockResolverCountWithQueryTxnStatusCommitted.Inc()
   607  			}
   608  
   609  			status.commitTS = cmdResp.CommitVersion
   610  			lr.saveResolved(txnID, status)
   611  		}
   612  
   613  		return status, nil
   614  	}
   615  }
   616  
   617  // asyncResolveData is data contributed by multiple goroutines when resolving locks using the async commit protocol. All
   618  // data should be protected by the mutex field.
   619  type asyncResolveData struct {
   620  	mutex sync.Mutex
   621  	// If any key has been committed (missingLock is true), then this is the commit ts. In that case, all locks should
   622  	// be committed with the same commit timestamp. If no locks have been committed (missingLock is false), then we will
   623  	// use max(all min commit ts) from all locks; i.e., it is the commit ts we should use. Note that a secondary dagger's
   624  	// commit ts may or may not be the same as the primary dagger's min commit ts.
   625  	commitTs    uint64
   626  	keys        [][]byte
   627  	missingLock bool
   628  }
   629  
   630  // addKeys adds the keys from locks to data, keeping other fields up to date. startTS and commitTS are for the
   631  // transaction being resolved.
   632  //
   633  // In the async commit protocol when checking locks, we send a list of keys to check and get back a list of locks. There
   634  // will be a dagger for every key which is locked. If there are fewer locks than keys, then a dagger is missing because it
   635  // has been committed, rolled back, or was never locked.
   636  //
   637  // In this function, locks is the list of locks, and expected is the number of keys. asyncResolveData.missingLock will be
   638  // set to true if the lengths don't match. If the lengths do match, then the locks are added to asyncResolveData.locks
   639  // and will need to be resolved by the caller.
   640  func (data *asyncResolveData) addKeys(locks []*ekvrpcpb.LockInfo, expected int, startTS uint64, commitTS uint64) error {
   641  	data.mutex.Lock()
   642  	defer data.mutex.Unlock()
   643  
   644  	// Check locks to see if any have been committed or rolled back.
   645  	if len(locks) < expected {
   646  		logutil.BgLogger().Debug("addKeys: dagger has been committed or rolled back", zap.Uint64("commit ts", commitTS), zap.Uint64("start ts", startTS))
   647  		// A dagger is missing - the transaction must either have been rolled back or committed.
   648  		if !data.missingLock {
   649  			// commitTS == 0 => dagger has been rolled back.
   650  			if commitTS != 0 && commitTS < data.commitTs {
   651  				return errors.Errorf("commit TS must be greater or equal to min commit TS: commit ts: %v, min commit ts: %v", commitTS, data.commitTs)
   652  			}
   653  			data.commitTs = commitTS
   654  		}
   655  		data.missingLock = true
   656  
   657  		if data.commitTs != commitTS {
   658  			return errors.Errorf("commit TS mismatch in async commit recovery: %v and %v", data.commitTs, commitTS)
   659  		}
   660  
   661  		// We do not need to resolve the remaining locks because EinsteinDB will have resolved them as appropriate.
   662  		return nil
   663  	}
   664  
   665  	logutil.BgLogger().Debug("addKeys: all locks present", zap.Uint64("start ts", startTS))
   666  	// Save all locks to be resolved.
   667  	for _, lockInfo := range locks {
   668  		if lockInfo.LockVersion != startTS {
   669  			err := errors.Errorf("unexpected timestamp, expected: %v, found: %v", startTS, lockInfo.LockVersion)
   670  			logutil.BgLogger().Error("addLocks error", zap.Error(err))
   671  			return err
   672  		}
   673  
   674  		if !data.missingLock && lockInfo.MinCommitTs > data.commitTs {
   675  			data.commitTs = lockInfo.MinCommitTs
   676  		}
   677  		data.keys = append(data.keys, lockInfo.Key)
   678  	}
   679  
   680  	return nil
   681  }
   682  
   683  func (lr *LockResolver) checkSecondaries(bo *Backoffer, txnID uint64, curKeys [][]byte, curRegionID RegionVerID, shared *asyncResolveData) error {
   684  	checkReq := &ekvrpcpb.CheckSecondaryLocksRequest{
   685  		Keys:         curKeys,
   686  		StartVersion: txnID,
   687  	}
   688  	req := einsteindbrpc.NewRequest(einsteindbrpc.CmdCheckSecondaryLocks, checkReq)
   689  	einsteindbLockResolverCountWithQueryCheckSecondaryLocks.Inc()
   690  	resp, err := lr.causetstore.SendReq(bo, req, curRegionID, readTimeoutShort)
   691  	if err != nil {
   692  		return errors.Trace(err)
   693  	}
   694  	regionErr, err := resp.GetRegionError()
   695  	if err != nil {
   696  		return errors.Trace(err)
   697  	}
   698  	if regionErr != nil {
   699  		err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
   700  		if err != nil {
   701  			return errors.Trace(err)
   702  		}
   703  
   704  		logutil.BgLogger().Debug("checkSecondaries: region error, regrouping", zap.Uint64("txn id", txnID), zap.Uint64("region", curRegionID.GetID()))
   705  
   706  		// If regions have changed, then we might need to regroup the keys. Since this should be rare and for the sake
   707  		// of simplicity, we will resolve regions sequentially.
   708  		regions, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, curKeys, nil)
   709  		if err != nil {
   710  			return errors.Trace(err)
   711  		}
   712  		for regionID, keys := range regions {
   713  			// Recursion will terminate because the resolve request succeeds or the Backoffer reaches its limit.
   714  			if err = lr.checkSecondaries(bo, txnID, keys, regionID, shared); err != nil {
   715  				return err
   716  			}
   717  		}
   718  		return nil
   719  	}
   720  	if resp.Resp == nil {
   721  		return errors.Trace(ErrBodyMissing)
   722  	}
   723  
   724  	checkResp := resp.Resp.(*ekvrpcpb.CheckSecondaryLocksResponse)
   725  	return shared.addKeys(checkResp.Locks, len(curKeys), txnID, checkResp.CommitTs)
   726  }
   727  
   728  // resolveLockAsync resolves l assuming it was locked using the async commit protocol.
   729  func (lr *LockResolver) resolveLockAsync(bo *Backoffer, l *Lock, status TxnStatus) error {
   730  	einsteindbLockResolverCountWithResolveAsync.Inc()
   731  
   732  	resolveData, err := lr.checkAllSecondaries(bo, l, &status)
   733  	if err != nil {
   734  		return err
   735  	}
   736  
   737  	status.commitTS = resolveData.commitTs
   738  
   739  	resolveData.keys = append(resolveData.keys, l.Primary)
   740  	keysByRegion, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, resolveData.keys, nil)
   741  	if err != nil {
   742  		return errors.Trace(err)
   743  	}
   744  
   745  	logutil.BgLogger().Info("resolve async commit", zap.Uint64("startTS", l.TxnID), zap.Uint64("commitTS", status.commitTS))
   746  
   747  	errChan := make(chan error, len(keysByRegion))
   748  	// Resolve every dagger in the transaction.
   749  	for region, locks := range keysByRegion {
   750  		curLocks := locks
   751  		curRegion := region
   752  		go func() {
   753  			errChan <- lr.resolveRegionLocks(bo, l, curRegion, curLocks, status)
   754  		}()
   755  	}
   756  
   757  	var errs []string
   758  	for range keysByRegion {
   759  		err1 := <-errChan
   760  		if err1 != nil {
   761  			errs = append(errs, err1.Error())
   762  		}
   763  	}
   764  
   765  	if len(errs) > 0 {
   766  		return errors.Errorf("async commit recovery (sending ResolveLock) finished with errors: %v", errs)
   767  	}
   768  
   769  	return nil
   770  }
   771  
   772  // checkAllSecondaries checks the secondary locks of an async commit transaction to find out the final
   773  // status of the transaction
   774  func (lr *LockResolver) checkAllSecondaries(bo *Backoffer, l *Lock, status *TxnStatus) (*asyncResolveData, error) {
   775  	regions, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, status.primaryLock.Secondaries, nil)
   776  	if err != nil {
   777  		return nil, errors.Trace(err)
   778  	}
   779  
   780  	shared := asyncResolveData{
   781  		mutex:       sync.Mutex{},
   782  		commitTs:    status.primaryLock.MinCommitTs,
   783  		keys:        [][]byte{},
   784  		missingLock: false,
   785  	}
   786  
   787  	errChan := make(chan error, len(regions))
   788  
   789  	for regionID, keys := range regions {
   790  		curRegionID := regionID
   791  		curKeys := keys
   792  
   793  		go func() {
   794  			errChan <- lr.checkSecondaries(bo, l.TxnID, curKeys, curRegionID, &shared)
   795  		}()
   796  	}
   797  
   798  	var errs []string
   799  	for range regions {
   800  		err1 := <-errChan
   801  		if err1 != nil {
   802  			errs = append(errs, err1.Error())
   803  		}
   804  	}
   805  
   806  	if len(errs) > 0 {
   807  		return nil, errors.Errorf("async commit recovery (sending CheckSecondaryLocks) finished with errors: %v", errs)
   808  	}
   809  
   810  	// TODO(nrc, cfzjywxk) schemaReplicant lease check
   811  
   812  	return &shared, nil
   813  }
   814  
   815  // resolveRegionLocks is essentially the same as resolveLock, but we resolve all keys in the same region at the same time.
   816  func (lr *LockResolver) resolveRegionLocks(bo *Backoffer, l *Lock, region RegionVerID, keys [][]byte, status TxnStatus) error {
   817  	lreq := &ekvrpcpb.ResolveLockRequest{
   818  		StartVersion: l.TxnID,
   819  	}
   820  	if status.IsCommitted() {
   821  		lreq.CommitVersion = status.CommitTS()
   822  	}
   823  	lreq.Keys = keys
   824  	req := einsteindbrpc.NewRequest(einsteindbrpc.CmdResolveLock, lreq)
   825  
   826  	resp, err := lr.causetstore.SendReq(bo, req, region, readTimeoutShort)
   827  	if err != nil {
   828  		return errors.Trace(err)
   829  	}
   830  
   831  	regionErr, err := resp.GetRegionError()
   832  	if err != nil {
   833  		return errors.Trace(err)
   834  	}
   835  	if regionErr != nil {
   836  		err := bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
   837  		if err != nil {
   838  			return errors.Trace(err)
   839  		}
   840  
   841  		logutil.BgLogger().Info("resolveRegionLocks region error, regrouping", zap.String("dagger", l.String()), zap.Uint64("region", region.GetID()))
   842  
   843  		// Regroup locks.
   844  		regions, _, err := lr.causetstore.GetRegionCache().GroupKeysByRegion(bo, keys, nil)
   845  		if err != nil {
   846  			return errors.Trace(err)
   847  		}
   848  		for regionID, keys := range regions {
   849  			// Recursion will terminate because the resolve request succeeds or the Backoffer reaches its limit.
   850  			if err = lr.resolveRegionLocks(bo, l, regionID, keys, status); err != nil {
   851  				return err
   852  			}
   853  		}
   854  		return nil
   855  	}
   856  	if resp.Resp == nil {
   857  		return errors.Trace(ErrBodyMissing)
   858  	}
   859  	cmdResp := resp.Resp.(*ekvrpcpb.ResolveLockResponse)
   860  	if keyErr := cmdResp.GetError(); keyErr != nil {
   861  		err = errors.Errorf("unexpected resolve err: %s, dagger: %v", keyErr, l)
   862  		logutil.BgLogger().Error("resolveLock error", zap.Error(err))
   863  	}
   864  
   865  	return nil
   866  }
   867  
   868  func (lr *LockResolver) resolveLock(bo *Backoffer, l *Lock, status TxnStatus, lite bool, cleanRegions map[RegionVerID]struct{}) error {
   869  	einsteindbLockResolverCountWithResolveLocks.Inc()
   870  	resolveLite := lite || l.TxnSize < bigTxnThreshold
   871  	for {
   872  		loc, err := lr.causetstore.GetRegionCache().LocateKey(bo, l.Key)
   873  		if err != nil {
   874  			return errors.Trace(err)
   875  		}
   876  		if _, ok := cleanRegions[loc.Region]; ok {
   877  			return nil
   878  		}
   879  		lreq := &ekvrpcpb.ResolveLockRequest{
   880  			StartVersion: l.TxnID,
   881  		}
   882  		if status.IsCommitted() {
   883  			lreq.CommitVersion = status.CommitTS()
   884  		} else {
   885  			logutil.BgLogger().Info("resolveLock rollback", zap.String("dagger", l.String()))
   886  		}
   887  
   888  		if resolveLite {
   889  			// Only resolve specified keys when it is a small transaction,
   890  			// prevent from scanning the whole region in this case.
   891  			einsteindbLockResolverCountWithResolveLockLite.Inc()
   892  			lreq.Keys = [][]byte{l.Key}
   893  		}
   894  		req := einsteindbrpc.NewRequest(einsteindbrpc.CmdResolveLock, lreq)
   895  		resp, err := lr.causetstore.SendReq(bo, req, loc.Region, readTimeoutShort)
   896  		if err != nil {
   897  			return errors.Trace(err)
   898  		}
   899  		regionErr, err := resp.GetRegionError()
   900  		if err != nil {
   901  			return errors.Trace(err)
   902  		}
   903  		if regionErr != nil {
   904  			err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
   905  			if err != nil {
   906  				return errors.Trace(err)
   907  			}
   908  			continue
   909  		}
   910  		if resp.Resp == nil {
   911  			return errors.Trace(ErrBodyMissing)
   912  		}
   913  		cmdResp := resp.Resp.(*ekvrpcpb.ResolveLockResponse)
   914  		if keyErr := cmdResp.GetError(); keyErr != nil {
   915  			err = errors.Errorf("unexpected resolve err: %s, dagger: %v", keyErr, l)
   916  			logutil.BgLogger().Error("resolveLock error", zap.Error(err))
   917  			return err
   918  		}
   919  		if !resolveLite {
   920  			cleanRegions[loc.Region] = struct{}{}
   921  		}
   922  		return nil
   923  	}
   924  }
   925  
   926  func (lr *LockResolver) resolvePessimisticLock(bo *Backoffer, l *Lock, cleanRegions map[RegionVerID]struct{}) error {
   927  	einsteindbLockResolverCountWithResolveLocks.Inc()
   928  	for {
   929  		loc, err := lr.causetstore.GetRegionCache().LocateKey(bo, l.Key)
   930  		if err != nil {
   931  			return errors.Trace(err)
   932  		}
   933  		if _, ok := cleanRegions[loc.Region]; ok {
   934  			return nil
   935  		}
   936  		forUFIDelateTS := l.LockForUFIDelateTS
   937  		if forUFIDelateTS == 0 {
   938  			forUFIDelateTS = math.MaxUint64
   939  		}
   940  		pessimisticRollbackReq := &ekvrpcpb.PessimisticRollbackRequest{
   941  			StartVersion:   l.TxnID,
   942  			ForUFIDelateTs: forUFIDelateTS,
   943  			Keys:           [][]byte{l.Key},
   944  		}
   945  		req := einsteindbrpc.NewRequest(einsteindbrpc.CmdPessimisticRollback, pessimisticRollbackReq)
   946  		resp, err := lr.causetstore.SendReq(bo, req, loc.Region, readTimeoutShort)
   947  		if err != nil {
   948  			return errors.Trace(err)
   949  		}
   950  		regionErr, err := resp.GetRegionError()
   951  		if err != nil {
   952  			return errors.Trace(err)
   953  		}
   954  		if regionErr != nil {
   955  			err = bo.Backoff(BoRegionMiss, errors.New(regionErr.String()))
   956  			if err != nil {
   957  				return errors.Trace(err)
   958  			}
   959  			continue
   960  		}
   961  		if resp.Resp == nil {
   962  			return errors.Trace(ErrBodyMissing)
   963  		}
   964  		cmdResp := resp.Resp.(*ekvrpcpb.PessimisticRollbackResponse)
   965  		if keyErr := cmdResp.GetErrors(); len(keyErr) > 0 {
   966  			err = errors.Errorf("unexpected resolve pessimistic dagger err: %s, dagger: %v", keyErr[0], l)
   967  			logutil.Logger(bo.ctx).Error("resolveLock error", zap.Error(err))
   968  			return err
   969  		}
   970  		return nil
   971  	}
   972  }