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  }