github.com/vescale/zgraph@v0.0.0-20230410094002-959c02d50f95/storage/latch/latch.go (about)

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