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  }