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 }