github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/regionspan/region_range_lock.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 regionspan 15 16 import ( 17 "bytes" 18 "context" 19 "encoding/hex" 20 "fmt" 21 "math" 22 "sync" 23 "sync/atomic" 24 25 "github.com/google/btree" 26 "github.com/pingcap/log" 27 "go.uber.org/zap" 28 ) 29 30 type rangeTsEntry struct { 31 // Only startKey is necessary. End key can be inferred by the next item since the map always keeps a continuous 32 // range. 33 startKey []byte 34 ts uint64 35 } 36 37 func rangeTsEntryWithKey(key []byte) *rangeTsEntry { 38 return &rangeTsEntry{ 39 startKey: key, 40 } 41 } 42 43 func (e *rangeTsEntry) Less(than btree.Item) bool { 44 return bytes.Compare(e.startKey, than.(*rangeTsEntry).startKey) < 0 45 } 46 47 // RangeTsMap represents a map from key range to a timestamp. It supports range set and calculating min value among a 48 // a specified range. 49 type RangeTsMap struct { 50 m *btree.BTree 51 } 52 53 // NewRangeTsMap creates a RangeTsMap. 54 func NewRangeTsMap(startKey, endKey []byte, startTs uint64) *RangeTsMap { 55 m := &RangeTsMap{ 56 m: btree.New(16), 57 } 58 m.Set(startKey, endKey, startTs) 59 return m 60 } 61 62 // Set sets the corresponding ts of the given range to the specified value. 63 func (m *RangeTsMap) Set(startKey, endKey []byte, ts uint64) { 64 if m.m.Get(rangeTsEntryWithKey(endKey)) == nil { 65 // To calculate the minimal ts, the default value is math.MaxUint64 66 tailTs := uint64(math.MaxUint64) 67 m.m.DescendLessOrEqual(rangeTsEntryWithKey(endKey), func(i btree.Item) bool { 68 tailTs = i.(*rangeTsEntry).ts 69 return false 70 }) 71 m.m.ReplaceOrInsert(&rangeTsEntry{ 72 startKey: endKey, 73 ts: tailTs, 74 }) 75 } 76 77 entriesToDelete := make([]*rangeTsEntry, 0) 78 m.m.AscendRange(rangeTsEntryWithKey(startKey), rangeTsEntryWithKey(endKey), func(i btree.Item) bool { 79 entriesToDelete = append(entriesToDelete, i.(*rangeTsEntry)) 80 return true 81 }) 82 83 for _, e := range entriesToDelete { 84 m.m.Delete(e) 85 } 86 87 m.m.ReplaceOrInsert(&rangeTsEntry{ 88 startKey: startKey, 89 ts: ts, 90 }) 91 } 92 93 // GetMin gets the min ts value among the given range. endKey must be greater than startKey. 94 func (m *RangeTsMap) GetMin(startKey, endKey []byte) uint64 { 95 var ts uint64 = math.MaxUint64 96 m.m.DescendLessOrEqual(rangeTsEntryWithKey(startKey), func(i btree.Item) bool { 97 ts = i.(*rangeTsEntry).ts 98 return false 99 }) 100 m.m.AscendRange(rangeTsEntryWithKey(startKey), rangeTsEntryWithKey(endKey), func(i btree.Item) bool { 101 thisTs := i.(*rangeTsEntry).ts 102 if ts > thisTs { 103 ts = thisTs 104 } 105 return true 106 }) 107 return ts 108 } 109 110 type rangeLockEntry struct { 111 startKey []byte 112 endKey []byte 113 regionID uint64 114 version uint64 115 waiters []chan<- interface{} 116 } 117 118 func rangeLockEntryWithKey(key []byte) *rangeLockEntry { 119 return &rangeLockEntry{ 120 startKey: key, 121 } 122 } 123 124 func (e *rangeLockEntry) Less(than btree.Item) bool { 125 return bytes.Compare(e.startKey, than.(*rangeLockEntry).startKey) < 0 126 } 127 128 func (e *rangeLockEntry) String() string { 129 return fmt.Sprintf("region %v [%v, %v), version %v, %d waiters", 130 e.regionID, 131 hex.EncodeToString(e.startKey), 132 hex.EncodeToString(e.endKey), 133 e.version, 134 len(e.waiters)) 135 } 136 137 var currentID uint64 = 0 138 139 func allocID() uint64 { 140 return atomic.AddUint64(¤tID, 1) 141 } 142 143 // RegionRangeLock is specifically used for kv client to manage exclusive region ranges. Acquiring lock will be blocked 144 // if part of its range is already locked. It also manages checkpoint ts of all ranges. The ranges are marked by a 145 // version number, which should comes from the Region's Epoch version. The version is used to compare which range is 146 // new and which is old if two ranges are overlapping. 147 type RegionRangeLock struct { 148 mu sync.Mutex 149 rangeCheckpointTs *RangeTsMap 150 rangeLock *btree.BTree 151 regionIDLock map[uint64]*rangeLockEntry 152 // ID to identify different RegionRangeLock instances, so logs of different instances can be distinguished. 153 id uint64 154 } 155 156 // NewRegionRangeLock creates a new RegionRangeLock. 157 func NewRegionRangeLock(startKey, endKey []byte, startTs uint64) *RegionRangeLock { 158 return &RegionRangeLock{ 159 rangeCheckpointTs: NewRangeTsMap(startKey, endKey, startTs), 160 rangeLock: btree.New(16), 161 regionIDLock: make(map[uint64]*rangeLockEntry), 162 id: allocID(), 163 } 164 } 165 166 func (l *RegionRangeLock) getOverlappedEntries(startKey, endKey []byte, regionID uint64) []*rangeLockEntry { 167 regionIDFound := false 168 169 overlappingRanges := make([]*rangeLockEntry, 0) 170 l.rangeLock.DescendLessOrEqual(rangeLockEntryWithKey(startKey), func(i btree.Item) bool { 171 entry := i.(*rangeLockEntry) 172 if bytes.Compare(entry.startKey, startKey) < 0 && 173 bytes.Compare(startKey, entry.endKey) < 0 { 174 overlappingRanges = append(overlappingRanges, entry) 175 if entry.regionID == regionID { 176 regionIDFound = true 177 } 178 } 179 return false 180 }) 181 l.rangeLock.AscendRange(rangeLockEntryWithKey(startKey), rangeLockEntryWithKey(endKey), func(i btree.Item) bool { 182 entry := i.(*rangeLockEntry) 183 overlappingRanges = append(overlappingRanges, entry) 184 if entry.regionID == regionID { 185 regionIDFound = true 186 } 187 return true 188 }) 189 190 // The entry with the same regionID should also be checked. 191 if !regionIDFound { 192 entry, ok := l.regionIDLock[regionID] 193 if ok { 194 overlappingRanges = append(overlappingRanges, entry) 195 } 196 } 197 198 return overlappingRanges 199 } 200 201 func (l *RegionRangeLock) tryLockRange(startKey, endKey []byte, regionID, version uint64) (LockRangeResult, []<-chan interface{}) { 202 l.mu.Lock() 203 defer l.mu.Unlock() 204 205 overlappingEntries := l.getOverlappedEntries(startKey, endKey, regionID) 206 207 if len(overlappingEntries) == 0 { 208 checkpointTs := l.rangeCheckpointTs.GetMin(startKey, endKey) 209 newEntry := &rangeLockEntry{ 210 startKey: startKey, 211 endKey: endKey, 212 regionID: regionID, 213 version: version, 214 } 215 l.rangeLock.ReplaceOrInsert(newEntry) 216 l.regionIDLock[regionID] = newEntry 217 218 log.Info("range locked", zap.Uint64("lockID", l.id), zap.Uint64("regionID", regionID), 219 zap.String("startKey", hex.EncodeToString(startKey)), zap.String("endKey", hex.EncodeToString(endKey)), 220 zap.Uint64("checkpointTs", checkpointTs)) 221 222 return LockRangeResult{ 223 Status: LockRangeStatusSuccess, 224 CheckpointTs: checkpointTs, 225 }, nil 226 } 227 228 // Format overlapping ranges for printing log 229 var overlapStr []string 230 for _, r := range overlappingEntries { 231 overlapStr = append(overlapStr, fmt.Sprintf("regionID: %v, ver: %v, start: %v, end: %v", 232 r.regionID, r.version, hex.EncodeToString(r.startKey), hex.EncodeToString(r.endKey))) // DEBUG 233 } 234 235 isStale := false 236 for _, r := range overlappingEntries { 237 if r.version >= version { 238 isStale = true 239 break 240 } 241 } 242 if isStale { 243 retryRanges := make([]ComparableSpan, 0) 244 currentRangeStartKey := startKey 245 246 log.Info("tryLockRange stale", zap.Uint64("lockID", l.id), zap.Uint64("regionID", regionID), 247 zap.String("startKey", hex.EncodeToString(startKey)), zap.String("endKey", hex.EncodeToString(endKey)), zap.Strings("allOverlapping", overlapStr)) // DEBUG 248 249 for _, r := range overlappingEntries { 250 // Ignore the totally-disjointed range which may be added to the list because of 251 // searching by regionID. 252 if bytes.Compare(r.endKey, startKey) <= 0 || bytes.Compare(endKey, r.startKey) <= 0 { 253 continue 254 } 255 // The rest should come from range searching and is sorted in increasing order, and they 256 // must intersect with the current given range. 257 if bytes.Compare(currentRangeStartKey, r.startKey) < 0 { 258 retryRanges = append(retryRanges, ComparableSpan{Start: currentRangeStartKey, End: r.startKey}) 259 } 260 currentRangeStartKey = r.endKey 261 } 262 if bytes.Compare(currentRangeStartKey, endKey) < 0 { 263 retryRanges = append(retryRanges, ComparableSpan{Start: currentRangeStartKey, End: endKey}) 264 } 265 266 return LockRangeResult{ 267 Status: LockRangeStatusStale, 268 RetryRanges: retryRanges, 269 }, nil 270 } 271 272 var signalChs []<-chan interface{} 273 274 for _, r := range overlappingEntries { 275 ch := make(chan interface{}, 1) 276 signalChs = append(signalChs, ch) 277 r.waiters = append(r.waiters, ch) 278 279 } 280 281 log.Info("lock range blocked", zap.Uint64("lockID", l.id), zap.Uint64("regionID", regionID), 282 zap.String("startKey", hex.EncodeToString(startKey)), zap.String("endKey", hex.EncodeToString(endKey)), zap.Strings("blockedBy", overlapStr)) // DEBUG 283 284 return LockRangeResult{ 285 Status: LockRangeStatusWait, 286 }, signalChs 287 } 288 289 // LockRange locks a range with specified version. 290 func (l *RegionRangeLock) LockRange(ctx context.Context, startKey, endKey []byte, regionID, version uint64) LockRangeResult { 291 res, signalChs := l.tryLockRange(startKey, endKey, regionID, version) 292 293 if res.Status != LockRangeStatusWait { 294 return res 295 } 296 297 res.WaitFn = func() LockRangeResult { 298 signalChs1 := signalChs 299 var res1 LockRangeResult 300 for { 301 for _, ch := range signalChs1 { 302 select { 303 case <-ctx.Done(): 304 return LockRangeResult{Status: LockRangeStatusCancel} 305 case <-ch: 306 } 307 } 308 res1, signalChs1 = l.tryLockRange(startKey, endKey, regionID, version) 309 if res1.Status != LockRangeStatusWait { 310 return res1 311 } 312 } 313 } 314 315 return res 316 } 317 318 // UnlockRange unlocks a range and update checkpointTs of the range to specivied value. 319 func (l *RegionRangeLock) UnlockRange(startKey, endKey []byte, regionID, version uint64, checkpointTs uint64) { 320 l.mu.Lock() 321 defer l.mu.Unlock() 322 323 item := l.rangeLock.Get(rangeLockEntryWithKey(startKey)) 324 325 if item == nil { 326 log.Panic("unlocking a not locked range", 327 zap.Uint64("regionID", regionID), 328 zap.String("startKey", hex.EncodeToString(startKey)), 329 zap.String("endKey", hex.EncodeToString(endKey)), 330 zap.Uint64("version", version), 331 zap.Uint64("checkpointTs", checkpointTs)) 332 } 333 334 entry := item.(*rangeLockEntry) 335 if entry.regionID != regionID { 336 log.Panic("unlocked a range but regionID mismatch", 337 zap.Uint64("expectedRegionID", regionID), 338 zap.Uint64("foundRegionID", entry.regionID), 339 zap.String("startKey", hex.EncodeToString(startKey)), 340 zap.String("endKey", hex.EncodeToString(endKey))) 341 } 342 if entry != l.regionIDLock[regionID] { 343 log.Panic("range lock and region id lock mismatch when trying to unlock", 344 zap.Uint64("unlockingRegionID", regionID), 345 zap.String("rangeLockEntry", entry.String()), 346 zap.String("regionIDLockEntry", l.regionIDLock[regionID].String())) 347 } 348 delete(l.regionIDLock, regionID) 349 350 if entry.version != version || !bytes.Equal(entry.endKey, endKey) { 351 log.Panic("unlocking region doesn't match the locked region. "+ 352 "Locked: [%v, %v), version %v; Unlocking: [%v, %v), %v", 353 zap.Uint64("regionID", regionID), 354 zap.String("startKey", hex.EncodeToString(startKey)), 355 zap.String("endKey", hex.EncodeToString(endKey)), 356 zap.Uint64("version", version), 357 zap.Uint64("checkpointTs", checkpointTs), 358 zap.String("foundLockEntry", entry.String())) 359 } 360 361 for _, ch := range entry.waiters { 362 ch <- nil 363 } 364 365 i := l.rangeLock.Delete(entry) 366 if i == nil { 367 panic("unreachable") 368 } 369 l.rangeCheckpointTs.Set(startKey, endKey, checkpointTs) 370 log.Info("unlocked range", zap.Uint64("lockID", l.id), zap.Uint64("regionID", entry.regionID), 371 zap.String("startKey", hex.EncodeToString(startKey)), zap.String("endKey", hex.EncodeToString(endKey)), 372 zap.Uint64("checkpointTs", checkpointTs)) 373 } 374 375 const ( 376 // LockRangeStatusSuccess means a LockRange operation succeeded. 377 LockRangeStatusSuccess = 0 378 // LockRangeStatusWait means a LockRange operation is blocked and should wait for it being finished. 379 LockRangeStatusWait = 1 380 // LockRangeStatusStale means a LockRange operation is rejected because of the range's version is stale. 381 LockRangeStatusStale = 2 382 // LockRangeStatusCancel means a LockRange operation is cancelled. 383 LockRangeStatusCancel = 3 384 ) 385 386 // LockRangeResult represents the result of LockRange method of RegionRangeLock. 387 // If Status is LockRangeStatusSuccess, the CheckpointTs field will be the minimal checkpoint ts among the locked 388 // range. 389 // If Status is LockRangeStatusWait, it means the lock cannot be acquired immediately. WaitFn must be invoked to 390 // continue waiting and acquiring the lock. 391 // If Status is LockRangeStatusStale, it means the LockRange request is stale because there's already a overlapping 392 // locked range, whose version is greater or equals to the requested one. 393 type LockRangeResult struct { 394 Status int 395 CheckpointTs uint64 396 WaitFn func() LockRangeResult 397 RetryRanges []ComparableSpan 398 }