github.com/KinWaiYuen/client-go/v2@v2.5.4/internal/latch/latch.go (about)

     1  // Copyright 2021 TiKV Authors
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // NOTE: The code in this file is based on code from the
    16  // TiDB project, licensed under the Apache License v 2.0
    17  //
    18  // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/latch/latch.go
    19  //
    20  
    21  // Copyright 2018 PingCAP, Inc.
    22  //
    23  // Licensed under the Apache License, Version 2.0 (the "License");
    24  // you may not use this file except in compliance with the License.
    25  // You may obtain a copy of the License at
    26  //
    27  //     http://www.apache.org/licenses/LICENSE-2.0
    28  //
    29  // Unless required by applicable law or agreed to in writing, software
    30  // distributed under the License is distributed on an "AS IS" BASIS,
    31  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    32  // See the License for the specific language governing permissions and
    33  // limitations under the License.
    34  
    35  package latch
    36  
    37  import (
    38  	"bytes"
    39  	"math/bits"
    40  	"sort"
    41  	"sync"
    42  	"time"
    43  
    44  	"github.com/KinWaiYuen/client-go/v2/internal/logutil"
    45  	"github.com/cznic/mathutil"
    46  	"github.com/twmb/murmur3"
    47  	"go.uber.org/zap"
    48  )
    49  
    50  type node struct {
    51  	slotID      int
    52  	key         []byte
    53  	maxCommitTS uint64
    54  	value       *Lock
    55  
    56  	next *node
    57  }
    58  
    59  // latch stores a key's waiting transactions information.
    60  type latch struct {
    61  	queue   *node
    62  	count   int
    63  	waiting []*Lock
    64  	sync.Mutex
    65  }
    66  
    67  // Lock is the locks' information required for a transaction.
    68  type Lock struct {
    69  	keys [][]byte
    70  	// requiredSlots represents required slots.
    71  	// The slot IDs of the latches(keys) that a startTS must acquire before being able to processed.
    72  	requiredSlots []int
    73  	// acquiredCount represents the number of latches that the transaction has acquired.
    74  	// For status is stale, it include the latch whose front is current lock already.
    75  	acquiredCount int
    76  	// startTS represents current transaction's.
    77  	startTS uint64
    78  	// commitTS represents current transaction's.
    79  	commitTS uint64
    80  
    81  	wg      sync.WaitGroup
    82  	isStale bool
    83  }
    84  
    85  // acquireResult is the result type for acquire()
    86  type acquireResult int32
    87  
    88  const (
    89  	// acquireSuccess is a type constant for acquireResult.
    90  	// which means acquired success
    91  	acquireSuccess acquireResult = iota
    92  	// acquireLocked is a type constant for acquireResult
    93  	// which means still locked by other Lock.
    94  	acquireLocked
    95  	// acquireStale is a type constant for acquireResult
    96  	// which means current Lock's startTS is stale.
    97  	acquireStale
    98  )
    99  
   100  // IsStale returns whether the status is stale.
   101  func (l *Lock) IsStale() bool {
   102  	return l.isStale
   103  }
   104  
   105  func (l *Lock) isLocked() bool {
   106  	return !l.isStale && l.acquiredCount != len(l.requiredSlots)
   107  }
   108  
   109  // SetCommitTS sets the lock's commitTS.
   110  func (l *Lock) SetCommitTS(commitTS uint64) {
   111  	l.commitTS = commitTS
   112  }
   113  
   114  // Latches which are used for concurrency control.
   115  // Each latch is indexed by a slot's ID, hence the term latch and slot are used in interchangeable,
   116  // but conceptually a latch is a queue, and a slot is an index to the queue
   117  type Latches struct {
   118  	slots []latch
   119  }
   120  
   121  type bytesSlice [][]byte
   122  
   123  func (s bytesSlice) Len() int {
   124  	return len(s)
   125  }
   126  
   127  func (s bytesSlice) Swap(i, j int) {
   128  	s[i], s[j] = s[j], s[i]
   129  }
   130  
   131  func (s bytesSlice) Less(i, j int) bool {
   132  	return bytes.Compare(s[i], s[j]) < 0
   133  }
   134  
   135  // NewLatches create a Latches with fixed length,
   136  // the size will be rounded up to the power of 2.
   137  func NewLatches(size uint) *Latches {
   138  	powerOfTwoSize := 1 << uint32(bits.Len32(uint32(size-1)))
   139  	slots := make([]latch, powerOfTwoSize)
   140  	return &Latches{
   141  		slots: slots,
   142  	}
   143  }
   144  
   145  // genLock generates Lock for the transaction with startTS and keys.
   146  func (latches *Latches) genLock(startTS uint64, keys [][]byte) *Lock {
   147  	sort.Sort(bytesSlice(keys))
   148  	return &Lock{
   149  		keys:          keys,
   150  		requiredSlots: latches.genSlotIDs(keys),
   151  		acquiredCount: 0,
   152  		startTS:       startTS,
   153  	}
   154  }
   155  
   156  func (latches *Latches) genSlotIDs(keys [][]byte) []int {
   157  	slots := make([]int, 0, len(keys))
   158  	for _, key := range keys {
   159  		slots = append(slots, latches.slotID(key))
   160  	}
   161  	return slots
   162  }
   163  
   164  // slotID return slotID for current key.
   165  func (latches *Latches) slotID(key []byte) int {
   166  	return int(murmur3.Sum32(key)) & (len(latches.slots) - 1)
   167  }
   168  
   169  // acquire tries to acquire the lock for a transaction.
   170  func (latches *Latches) acquire(lock *Lock) acquireResult {
   171  	if lock.IsStale() {
   172  		return acquireStale
   173  	}
   174  	for lock.acquiredCount < len(lock.requiredSlots) {
   175  		status := latches.acquireSlot(lock)
   176  		if status != acquireSuccess {
   177  			return status
   178  		}
   179  	}
   180  	return acquireSuccess
   181  }
   182  
   183  // release releases all latches owned by the `lock` and returns the wakeup list.
   184  // Preconditions: the caller must ensure the transaction's status is not locked.
   185  func (latches *Latches) release(lock *Lock, wakeupList []*Lock) []*Lock {
   186  	wakeupList = wakeupList[:0]
   187  	for lock.acquiredCount > 0 {
   188  		if nextLock := latches.releaseSlot(lock); nextLock != nil {
   189  			wakeupList = append(wakeupList, nextLock)
   190  		}
   191  	}
   192  	return wakeupList
   193  }
   194  
   195  func (latches *Latches) releaseSlot(lock *Lock) (nextLock *Lock) {
   196  	key := lock.keys[lock.acquiredCount-1]
   197  	slotID := lock.requiredSlots[lock.acquiredCount-1]
   198  	latch := &latches.slots[slotID]
   199  	lock.acquiredCount--
   200  	latch.Lock()
   201  	defer latch.Unlock()
   202  
   203  	find := findNode(latch.queue, key)
   204  	if find.value != lock {
   205  		panic("releaseSlot wrong")
   206  	}
   207  	find.maxCommitTS = mathutil.MaxUint64(find.maxCommitTS, lock.commitTS)
   208  	find.value = nil
   209  	// Make a copy of the key, so latch does not reference the transaction's memory.
   210  	// If we do not do it, transaction memory can't be recycle by GC and there will
   211  	// be a leak.
   212  	copyKey := make([]byte, len(find.key))
   213  	copy(copyKey, find.key)
   214  	find.key = copyKey
   215  	if len(latch.waiting) == 0 {
   216  		return nil
   217  	}
   218  
   219  	var idx int
   220  	for idx = 0; idx < len(latch.waiting); idx++ {
   221  		waiting := latch.waiting[idx]
   222  		if bytes.Equal(waiting.keys[waiting.acquiredCount], key) {
   223  			break
   224  		}
   225  	}
   226  	// Wake up the first one in waiting queue.
   227  	if idx < len(latch.waiting) {
   228  		nextLock = latch.waiting[idx]
   229  		// Delete element latch.waiting[idx] from the array.
   230  		copy(latch.waiting[idx:], latch.waiting[idx+1:])
   231  		latch.waiting[len(latch.waiting)-1] = nil
   232  		latch.waiting = latch.waiting[:len(latch.waiting)-1]
   233  
   234  		if find.maxCommitTS > nextLock.startTS {
   235  			find.value = nextLock
   236  			nextLock.acquiredCount++
   237  			nextLock.isStale = true
   238  		}
   239  	}
   240  
   241  	return
   242  }
   243  
   244  func (latches *Latches) acquireSlot(lock *Lock) acquireResult {
   245  	key := lock.keys[lock.acquiredCount]
   246  	slotID := lock.requiredSlots[lock.acquiredCount]
   247  	latch := &latches.slots[slotID]
   248  	latch.Lock()
   249  	defer latch.Unlock()
   250  
   251  	// Try to recycle to limit the memory usage.
   252  	if latch.count >= latchListCount {
   253  		latch.recycle(lock.startTS)
   254  	}
   255  
   256  	find := findNode(latch.queue, key)
   257  	if find == nil {
   258  		tmp := &node{
   259  			slotID: slotID,
   260  			key:    key,
   261  			value:  lock,
   262  		}
   263  		tmp.next = latch.queue
   264  		latch.queue = tmp
   265  		latch.count++
   266  
   267  		lock.acquiredCount++
   268  		return acquireSuccess
   269  	}
   270  
   271  	if find.maxCommitTS > lock.startTS {
   272  		lock.isStale = true
   273  		return acquireStale
   274  	}
   275  
   276  	if find.value == nil {
   277  		find.value = lock
   278  		lock.acquiredCount++
   279  		return acquireSuccess
   280  	}
   281  
   282  	// Push the current transaction into waitingQueue.
   283  	latch.waiting = append(latch.waiting, lock)
   284  	return acquireLocked
   285  }
   286  
   287  // recycle is not thread safe, the latch should acquire its lock before executing this function.
   288  func (l *latch) recycle(currentTS uint64) int {
   289  	total := 0
   290  	fakeHead := node{next: l.queue}
   291  	prev := &fakeHead
   292  	for curr := prev.next; curr != nil; curr = curr.next {
   293  		if tsoSub(currentTS, curr.maxCommitTS) >= expireDuration && curr.value == nil {
   294  			l.count--
   295  			prev.next = curr.next
   296  			total++
   297  		} else {
   298  			prev = curr
   299  		}
   300  	}
   301  	l.queue = fakeHead.next
   302  	return total
   303  }
   304  
   305  func (latches *Latches) recycle(currentTS uint64) {
   306  	total := 0
   307  	for i := 0; i < len(latches.slots); i++ {
   308  		latch := &latches.slots[i]
   309  		latch.Lock()
   310  		total += latch.recycle(currentTS)
   311  		latch.Unlock()
   312  	}
   313  	logutil.BgLogger().Debug("recycle",
   314  		zap.Time("start at", time.Now()),
   315  		zap.Int("count", total))
   316  }
   317  
   318  func findNode(list *node, key []byte) *node {
   319  	for n := list; n != nil; n = n.next {
   320  		if bytes.Equal(n.key, key) {
   321  			return n
   322  		}
   323  	}
   324  	return nil
   325  }