github.com/safing/portbase@v0.19.5/utils/stablepool.go (about)

     1  package utils
     2  
     3  import "sync"
     4  
     5  // A StablePool is a drop-in replacement for sync.Pool that is slower, but
     6  // predictable.
     7  // A StablePool is a set of temporary objects that may be individually saved and
     8  // retrieved.
     9  //
    10  // In contrast to sync.Pool, items are not removed automatically. Every item
    11  // will be returned at some point. Items are returned in a FIFO manner in order
    12  // to evenly distribute usage of a set of items.
    13  //
    14  // A StablePool is safe for use by multiple goroutines simultaneously and must
    15  // not be copied after first use.
    16  type StablePool struct {
    17  	lock sync.Mutex
    18  
    19  	pool     []interface{}
    20  	cnt      int
    21  	getIndex int
    22  	putIndex int
    23  
    24  	// New optionally specifies a function to generate
    25  	// a value when Get would otherwise return nil.
    26  	// It may not be changed concurrently with calls to Get.
    27  	New func() interface{}
    28  }
    29  
    30  // Put adds x to the pool.
    31  func (p *StablePool) Put(x interface{}) {
    32  	if x == nil {
    33  		return
    34  	}
    35  
    36  	p.lock.Lock()
    37  	defer p.lock.Unlock()
    38  
    39  	// check if pool is full (or unitialized)
    40  	if p.cnt == len(p.pool) {
    41  		p.pool = append(p.pool, x)
    42  		p.cnt++
    43  		p.putIndex = p.cnt
    44  		return
    45  	}
    46  
    47  	// correct putIndex
    48  	p.putIndex %= len(p.pool)
    49  
    50  	// iterate the whole pool once to find a free spot
    51  	stopAt := p.putIndex - 1
    52  	for i := p.putIndex; i != stopAt; i = (i + 1) % len(p.pool) {
    53  		if p.pool[i] == nil {
    54  			p.pool[i] = x
    55  			p.cnt++
    56  			p.putIndex = i + 1
    57  			return
    58  		}
    59  	}
    60  }
    61  
    62  // Get returns the next item from the Pool, removes it from the Pool, and
    63  // returns it to the caller.
    64  // In contrast to sync.Pool, Get never ignores the pool.
    65  // Callers should not assume any relation between values passed to Put and
    66  // the values returned by Get.
    67  //
    68  // If Get would otherwise return nil and p.New is non-nil, Get returns
    69  // the result of calling p.New.
    70  func (p *StablePool) Get() interface{} {
    71  	p.lock.Lock()
    72  	defer p.lock.Unlock()
    73  
    74  	// check if pool is empty
    75  	if p.cnt == 0 {
    76  		if p.New != nil {
    77  			return p.New()
    78  		}
    79  		return nil
    80  	}
    81  
    82  	// correct getIndex
    83  	p.getIndex %= len(p.pool)
    84  
    85  	// iterate the whole pool to find an item
    86  	stopAt := p.getIndex - 1
    87  	for i := p.getIndex; i != stopAt; i = (i + 1) % len(p.pool) {
    88  		if p.pool[i] != nil {
    89  			x := p.pool[i]
    90  			p.pool[i] = nil
    91  			p.cnt--
    92  			p.getIndex = i + 1
    93  			return x
    94  		}
    95  	}
    96  
    97  	// if we ever get here, return a new item
    98  	if p.New != nil {
    99  		return p.New()
   100  	}
   101  	return nil
   102  }
   103  
   104  // Size returns the amount of items the pool currently holds.
   105  func (p *StablePool) Size() int {
   106  	p.lock.Lock()
   107  	defer p.lock.Unlock()
   108  
   109  	return p.cnt
   110  }
   111  
   112  // Max returns the amount of items the pool held at maximum.
   113  func (p *StablePool) Max() int {
   114  	p.lock.Lock()
   115  	defer p.lock.Unlock()
   116  
   117  	return len(p.pool)
   118  }