github.com/koko1123/flow-go-1@v0.29.6/module/mempool/herocache/backdata/heropool/pool.go (about) 1 package heropool 2 3 import ( 4 "math/rand" 5 6 "github.com/koko1123/flow-go-1/model/flow" 7 ) 8 9 type EjectionMode string 10 11 const ( 12 RandomEjection = EjectionMode("random-ejection") 13 LRUEjection = EjectionMode("lru-ejection") 14 NoEjection = EjectionMode("no-ejection") 15 ) 16 17 // EIndex is data type representing an entity index in Pool. 18 type EIndex uint32 19 20 // poolEntity represents the data type that is maintained by 21 type poolEntity struct { 22 PoolEntity 23 // owner maintains an external reference to the key associated with this entity. 24 // The key is maintained by the HeroCache, and entity is maintained by Pool. 25 owner uint64 26 27 // node keeps the link to the previous and next entities. 28 // When this entity is allocated, the node maintains the connections it to the next and previous (used) pool entities. 29 // When this entity is unallocated, the node maintains the connections to the next and previous unallocated (free) pool entities. 30 node link 31 } 32 33 type PoolEntity struct { 34 // Identity associated with this entity. 35 id flow.Identifier 36 37 // Actual entity itself. 38 entity flow.Entity 39 } 40 41 func (p PoolEntity) Id() flow.Identifier { 42 return p.id 43 } 44 45 func (p PoolEntity) Entity() flow.Entity { 46 return p.entity 47 } 48 49 type Pool struct { 50 size uint32 51 free state // keeps track of free slots. 52 used state // keeps track of allocated slots to cachedEntities. 53 poolEntities []poolEntity 54 ejectionMode EjectionMode 55 } 56 57 func NewHeroPool(sizeLimit uint32, ejectionMode EjectionMode) *Pool { 58 l := &Pool{ 59 free: state{ 60 head: poolIndex{index: 0}, 61 tail: poolIndex{index: 0}, 62 }, 63 used: state{ 64 head: poolIndex{index: 0}, 65 tail: poolIndex{index: 0}, 66 }, 67 poolEntities: make([]poolEntity, sizeLimit), 68 ejectionMode: ejectionMode, 69 } 70 71 l.initFreeEntities() 72 73 return l 74 } 75 76 // initFreeEntities initializes the free double linked-list with the indices of all cached entity poolEntities. 77 func (p *Pool) initFreeEntities() { 78 p.free.head.setPoolIndex(0) 79 p.free.tail.setPoolIndex(0) 80 81 for i := 1; i < len(p.poolEntities); i++ { 82 // appends slice index i to tail of free linked list 83 p.connect(p.free.tail, EIndex(i)) 84 // and updates its tail 85 p.free.tail.setPoolIndex(EIndex(i)) 86 } 87 } 88 89 // Add writes given entity into a poolEntity on the underlying entities linked-list. 90 // 91 // The first boolean return value (slotAvailable) says whether pool has an available slot. Pool goes out of available slots if 92 // it is full and no ejection is set. 93 // 94 // If the pool has an available slot (either empty or by ejection), then the second boolean returned value (ejectionOccurred) 95 // determines whether an ejection happened to make one slot free or not. Ejection happens if there is no available 96 // slot, and there is an ejection mode set. 97 func (p *Pool) Add(entityId flow.Identifier, entity flow.Entity, owner uint64) (i EIndex, slotAvailable bool, ejectionOccurred bool) { 98 entityIndex, slotAvailable, ejectionHappened := p.sliceIndexForEntity() 99 if slotAvailable { 100 p.poolEntities[entityIndex].entity = entity 101 p.poolEntities[entityIndex].id = entityId 102 p.poolEntities[entityIndex].owner = owner 103 p.poolEntities[entityIndex].node.next.setUndefined() 104 p.poolEntities[entityIndex].node.prev.setUndefined() 105 106 if p.used.head.isUndefined() { 107 // used list is empty, hence setting head of used list to current entityIndex. 108 p.used.head.setPoolIndex(entityIndex) 109 p.poolEntities[p.used.head.getSliceIndex()].node.prev.setUndefined() 110 } 111 112 if !p.used.tail.isUndefined() { 113 // links new entity to the tail 114 p.connect(p.used.tail, entityIndex) 115 } 116 117 // since we are appending to the used list, entityIndex also acts as tail of the list. 118 p.used.tail.setPoolIndex(entityIndex) 119 120 p.size++ 121 } 122 123 return entityIndex, slotAvailable, ejectionHappened 124 } 125 126 // Get returns entity corresponding to the entity index from the underlying list. 127 func (p Pool) Get(entityIndex EIndex) (flow.Identifier, flow.Entity, uint64) { 128 return p.poolEntities[entityIndex].id, p.poolEntities[entityIndex].entity, p.poolEntities[entityIndex].owner 129 } 130 131 // All returns all stored entities in this pool. 132 func (p Pool) All() []PoolEntity { 133 all := make([]PoolEntity, p.size) 134 next := p.used.head 135 136 for i := uint32(0); i < p.size; i++ { 137 e := p.poolEntities[next.getSliceIndex()] 138 all[i] = e.PoolEntity 139 next = e.node.next 140 } 141 142 return all 143 } 144 145 // Head returns the head of used items. Assuming no ejection happened and pool never goes beyond limit, Head returns 146 // the first inserted element. 147 func (p Pool) Head() (flow.Entity, bool) { 148 if p.used.head.isUndefined() { 149 return nil, false 150 } 151 e := p.poolEntities[p.used.head.getSliceIndex()] 152 return e.Entity(), true 153 } 154 155 // sliceIndexForEntity returns a slice index which hosts the next entity to be added to the list. 156 // 157 // The first boolean return value (hasAvailableSlot) says whether pool has an available slot. 158 // Pool goes out of available slots if it is full and no ejection is set. 159 // 160 // If the pool has an available slot (either empty or by ejection), then the second boolean returned value 161 // (ejectionOccurred) determines whether an ejection happened to make one slot free or not. 162 // Ejection happens if there is no available slot, and there is an ejection mode set. 163 func (p *Pool) sliceIndexForEntity() (i EIndex, hasAvailableSlot bool, ejectionOccurred bool) { 164 if p.free.head.isUndefined() { 165 // the free list is empty, so we are out of space, and we need to eject. 166 switch p.ejectionMode { 167 case NoEjection: 168 // pool is set for no ejection, hence, no slice index is selected, abort immediately. 169 return 0, false, false 170 case LRUEjection: 171 // LRU ejection 172 // the used head is the oldest entity, so we turn the used head to a free head here. 173 p.invalidateUsedHead() 174 return p.claimFreeHead(), true, true 175 case RandomEjection: 176 // we only eject randomly when the pool is full and random ejection is on. 177 randomIndex := EIndex(rand.Uint32() % p.size) 178 p.invalidateEntityAtIndex(randomIndex) 179 return p.claimFreeHead(), true, true 180 } 181 } 182 183 // claiming the head of free list as the slice index for the next entity to be added 184 return p.claimFreeHead(), true, false // returning false for no ejection. 185 } 186 187 // Size returns total number of entities that this list maintains. 188 func (p Pool) Size() uint32 { 189 return p.size 190 } 191 192 // getHeads returns entities corresponding to the used and free heads. 193 func (p Pool) getHeads() (*poolEntity, *poolEntity) { 194 var usedHead, freeHead *poolEntity 195 if !p.used.head.isUndefined() { 196 usedHead = &p.poolEntities[p.used.head.getSliceIndex()] 197 } 198 199 if !p.free.head.isUndefined() { 200 freeHead = &p.poolEntities[p.free.head.getSliceIndex()] 201 } 202 203 return usedHead, freeHead 204 } 205 206 // getTails returns entities corresponding to the used and free tails. 207 func (p Pool) getTails() (*poolEntity, *poolEntity) { 208 var usedTail, freeTail *poolEntity 209 if !p.used.tail.isUndefined() { 210 usedTail = &p.poolEntities[p.used.tail.getSliceIndex()] 211 } 212 213 if !p.free.tail.isUndefined() { 214 freeTail = &p.poolEntities[p.free.tail.getSliceIndex()] 215 } 216 217 return usedTail, freeTail 218 } 219 220 // connect links the prev and next nodes as the adjacent nodes in the double-linked list. 221 func (p *Pool) connect(prev poolIndex, next EIndex) { 222 p.poolEntities[prev.getSliceIndex()].node.next.setPoolIndex(next) 223 p.poolEntities[next].node.prev = prev 224 } 225 226 // invalidateUsedHead moves current used head forward by one node. It 227 // also removes the entity the invalidated head is presenting and appends the 228 // node represented by the used head to the tail of the free list. 229 func (p *Pool) invalidateUsedHead() EIndex { 230 headSliceIndex := p.used.head.getSliceIndex() 231 p.invalidateEntityAtIndex(headSliceIndex) 232 233 return headSliceIndex 234 } 235 236 // claimFreeHead moves the free head forward, and returns the slice index of the 237 // old free head to host a new entity. 238 func (p *Pool) claimFreeHead() EIndex { 239 oldFreeHeadIndex := p.free.head.getSliceIndex() 240 // moves head forward 241 p.free.head = p.poolEntities[oldFreeHeadIndex].node.next 242 // new head should point to an undefined prev, 243 // but we first check if list is not empty, i.e., 244 // head itself is not undefined. 245 if !p.free.head.isUndefined() { 246 p.poolEntities[p.free.head.getSliceIndex()].node.prev.setUndefined() 247 } 248 249 // also, we check if the old head and tail are aligned and, if so, update the 250 // tail as well. This happens when we claim the only existing 251 // node of the free list. 252 if p.free.tail.getSliceIndex() == oldFreeHeadIndex { 253 p.free.tail.setUndefined() 254 } 255 256 // clears pointers of claimed head 257 p.poolEntities[oldFreeHeadIndex].node.next.setUndefined() 258 p.poolEntities[oldFreeHeadIndex].node.prev.setUndefined() 259 260 return oldFreeHeadIndex 261 } 262 263 // Remove removes entity corresponding to given getSliceIndex from the list. 264 func (p *Pool) Remove(sliceIndex EIndex) { 265 p.invalidateEntityAtIndex(sliceIndex) 266 } 267 268 // invalidateEntityAtIndex invalidates the given getSliceIndex in the linked list by 269 // removing its corresponding linked-list node from the used linked list, and appending 270 // it to the tail of the free list. It also removes the entity that the invalidated node is presenting. 271 func (p *Pool) invalidateEntityAtIndex(sliceIndex EIndex) { 272 prev := p.poolEntities[sliceIndex].node.prev 273 next := p.poolEntities[sliceIndex].node.next 274 275 if sliceIndex != p.used.head.getSliceIndex() && sliceIndex != p.used.tail.getSliceIndex() { 276 // links next and prev elements for non-head and non-tail element 277 p.connect(prev, next.getSliceIndex()) 278 } 279 280 if sliceIndex == p.used.head.getSliceIndex() { 281 // invalidating used head 282 // moves head forward 283 oldUsedHead, _ := p.getHeads() 284 p.used.head = oldUsedHead.node.next 285 // new head should point to an undefined prev, 286 // but we first check if list is not empty, i.e., 287 // head itself is not undefined. 288 if !p.used.head.isUndefined() { 289 usedHead, _ := p.getHeads() 290 usedHead.node.prev.setUndefined() 291 } 292 } 293 294 if sliceIndex == p.used.tail.getSliceIndex() { 295 // invalidating used tail 296 // moves tail backward 297 oldUsedTail, _ := p.getTails() 298 p.used.tail = oldUsedTail.node.prev 299 // new head should point tail to an undefined next, 300 // but we first check if list is not empty, i.e., 301 // tail itself is not undefined. 302 if !p.used.tail.isUndefined() { 303 usedTail, _ := p.getTails() 304 usedTail.node.next.setUndefined() 305 } 306 } 307 308 // invalidates entity and adds it to free entities. 309 p.poolEntities[sliceIndex].id = flow.ZeroID 310 p.poolEntities[sliceIndex].entity = nil 311 p.poolEntities[sliceIndex].node.next.setUndefined() 312 p.poolEntities[sliceIndex].node.prev.setUndefined() 313 314 p.appendToFreeList(sliceIndex) 315 316 // decrements Size 317 p.size-- 318 } 319 320 // appendToFreeList appends linked-list node represented by getSliceIndex to tail of free list. 321 func (p *Pool) appendToFreeList(sliceIndex EIndex) { 322 if p.free.head.isUndefined() { 323 // free list is empty 324 p.free.head.setPoolIndex(sliceIndex) 325 p.free.tail.setPoolIndex(sliceIndex) 326 return 327 } 328 329 // appends to the tail, and updates the tail 330 p.connect(p.free.tail, sliceIndex) 331 p.free.tail.setPoolIndex(sliceIndex) 332 } 333 334 // isInvalidated returns true if linked-list node represented by getSliceIndex does not contain 335 // a valid entity. 336 func (p Pool) isInvalidated(sliceIndex EIndex) bool { 337 if p.poolEntities[sliceIndex].id != flow.ZeroID { 338 return false 339 } 340 341 if p.poolEntities[sliceIndex].entity != nil { 342 return false 343 } 344 345 return true 346 }