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(&currentID, 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  }