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 }