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