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 }