github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/txnutil/lock_resolver.go (about)

     1  // Copyright 2020 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 txnutil
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"time"
    20  
    21  	"github.com/pingcap/errors"
    22  	"github.com/pingcap/kvproto/pkg/kvrpcpb"
    23  	"github.com/pingcap/log"
    24  	"github.com/pingcap/tiflow/cdc/model"
    25  	tikverr "github.com/tikv/client-go/v2/error"
    26  	"github.com/tikv/client-go/v2/tikv"
    27  	"github.com/tikv/client-go/v2/tikvrpc"
    28  	"github.com/tikv/client-go/v2/txnkv"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  // LockResolver resolves lock in the given region.
    33  type LockResolver interface {
    34  	Resolve(ctx context.Context, regionID uint64, maxVersion uint64) error
    35  }
    36  
    37  type resolver struct {
    38  	kvStorage  tikv.Storage
    39  	changefeed model.ChangeFeedID
    40  }
    41  
    42  // NewLockerResolver returns a LockResolver.
    43  func NewLockerResolver(
    44  	kvStorage tikv.Storage, id model.ChangeFeedID,
    45  ) LockResolver {
    46  	return &resolver{
    47  		kvStorage:  kvStorage,
    48  		changefeed: id,
    49  	}
    50  }
    51  
    52  const scanLockLimit = 1024
    53  
    54  func (r *resolver) Resolve(ctx context.Context, regionID uint64, maxVersion uint64) (err error) {
    55  	var totalLocks []*txnkv.Lock
    56  
    57  	start := time.Now()
    58  
    59  	defer func() {
    60  		// Only log when there are locks or error to avoid log flooding.
    61  		if len(totalLocks) != 0 || err != nil {
    62  			cost := time.Since(start)
    63  			log.Info("resolve lock finishes",
    64  				zap.Uint64("regionID", regionID),
    65  				zap.Int("lockCount", len(totalLocks)),
    66  				zap.Any("locks", totalLocks),
    67  				zap.Uint64("maxVersion", maxVersion),
    68  				zap.String("namespace", r.changefeed.Namespace),
    69  				zap.String("changefeed", r.changefeed.ID),
    70  				zap.Duration("duration", cost),
    71  				zap.Error(err))
    72  		}
    73  	}()
    74  
    75  	// TODO test whether this function will kill active transaction
    76  	req := tikvrpc.NewRequest(tikvrpc.CmdScanLock, &kvrpcpb.ScanLockRequest{
    77  		MaxVersion: maxVersion,
    78  		Limit:      scanLockLimit,
    79  	})
    80  
    81  	bo := tikv.NewGcResolveLockMaxBackoffer(ctx)
    82  	var loc *tikv.KeyLocation
    83  	var key []byte
    84  	flushRegion := func() error {
    85  		var err error
    86  		loc, err = r.kvStorage.GetRegionCache().LocateRegionByID(bo, regionID)
    87  		if err != nil {
    88  			return err
    89  		}
    90  		key = loc.StartKey
    91  		return nil
    92  	}
    93  	if err := flushRegion(); err != nil {
    94  		return errors.Trace(err)
    95  	}
    96  	for {
    97  		select {
    98  		case <-ctx.Done():
    99  			return ctx.Err()
   100  		default:
   101  		}
   102  		req.ScanLock().StartKey = key
   103  		resp, err := r.kvStorage.SendReq(bo, req, loc.Region, tikv.ReadTimeoutMedium)
   104  		if err != nil {
   105  			return errors.Trace(err)
   106  		}
   107  		regionErr, err := resp.GetRegionError()
   108  		if err != nil {
   109  			return errors.Trace(err)
   110  		}
   111  		if regionErr != nil {
   112  			err = bo.Backoff(tikv.BoRegionMiss(), errors.New(regionErr.String()))
   113  			if err != nil {
   114  				return errors.Trace(err)
   115  			}
   116  			if err := flushRegion(); err != nil {
   117  				return errors.Trace(err)
   118  			}
   119  			continue
   120  		}
   121  		if resp.Resp == nil {
   122  			return errors.Trace(tikverr.ErrBodyMissing)
   123  		}
   124  		locksResp := resp.Resp.(*kvrpcpb.ScanLockResponse)
   125  		if locksResp.GetError() != nil {
   126  			return errors.Errorf("unexpected scanlock error: %s", locksResp)
   127  		}
   128  		locksInfo := locksResp.GetLocks()
   129  		locks := make([]*txnkv.Lock, len(locksInfo))
   130  		for i := range locksInfo {
   131  			locks[i] = txnkv.NewLock(locksInfo[i])
   132  		}
   133  		totalLocks = append(totalLocks, locks...)
   134  
   135  		_, err1 := r.kvStorage.GetLockResolver().ResolveLocks(bo, 0, locks)
   136  		if err1 != nil {
   137  			return errors.Trace(err1)
   138  		}
   139  		if len(locks) < scanLockLimit {
   140  			key = loc.EndKey
   141  		} else {
   142  			key = locks[len(locks)-1].Key
   143  		}
   144  
   145  		if len(key) == 0 || (len(loc.EndKey) != 0 && bytes.Compare(key, loc.EndKey) >= 0) {
   146  			break
   147  		}
   148  		bo = tikv.NewGcResolveLockMaxBackoffer(ctx)
   149  	}
   150  	return nil
   151  }