github.com/xmidt-org/webpa-common@v1.11.9/store/circularPool.go (about) 1 package store 2 3 import ( 4 "errors" 5 "time" 6 ) 7 8 var ( 9 ErrorInvalidMaxSize = errors.New("The maxSize of a pool must be positive") 10 ErrorInvalidInitialSize = errors.New("The initialSize of a pool cannot be larger than maxSize") 11 ErrorNewRequired = errors.New("A new function is required") 12 ErrorPoolClosed = errors.New("The pool is already closed") 13 ) 14 15 // Pool represents an object pool. sync.Pool satisfies this interface, 16 // as does CircularPool from this package. 17 type Pool interface { 18 // Get fetches an object from the pool. Implementations will vary on 19 // the behavior of an empty pool. 20 Get() interface{} 21 22 // Put inserts and object into the pool. Get and Put are typically used 23 // with the same type, though that is not necessarily enforced. 24 Put(value interface{}) 25 } 26 27 // CircularPool represents a fixed-size pool whose objects rotate in 28 // and out. Like sync.Pool, clients should not expect any relationship 29 // between objects obtained from a CircularPool and objects put into a 30 // CircularPool. 31 // 32 // Get will block until an object is returned to the pool. GetOrNew, however, 33 // will create a new object if the pool is exhausted. This allows a client a 34 // choice between being limited by the pool or treating the pool as the minimum 35 // amount of objects to keep around. 36 // 37 // Put will drop objects if invoked when the pool is full. This can happen 38 // if a client uses GetOrNew. 39 // 40 // The main difference between CircularPool and sync.Pool is that objects within 41 // a CircularPool will not get deallocated. This makes a CircularPool appropriate 42 // for times when a statically-sized pool of permanent objects is desired. 43 // 44 // A CircularPool is a very flexible concurrent data structure. It can be used as a simple pool 45 // of objects for canonicalization. It can also be used for rate limiting or any situation where 46 // a lease is required. 47 type CircularPool interface { 48 Pool 49 50 // TryGet will never block. It will attempt to grab an object from the pool, 51 // returning nil and false if it cannot do so. 52 TryGet() (interface{}, bool) 53 54 // GetCancel will wait for an available object with cancellation semantics 55 // similar to golang's Context. Any activity, such as close, on the supplied 56 // channel will interrupt the wait and this method will return nil and false. 57 GetCancel(<-chan struct{}) (interface{}, bool) 58 59 // GetTimeout will wait for an available object for the specified timeout. 60 // If the timeout is not positive, it behaves exactly as TryGet. Otherwise, this 61 // method waits the given duration for an available object, returning nil 62 // and false if the timeout elapses without an available object. 63 GetTimeout(time.Duration) (interface{}, bool) 64 65 // GetOrNew will never block. If the pool is exhausted, the new function 66 // will be used to create a new object. This can be used to allow the number 67 // objects to grow beyond the size of the pool. However, when Put is called, 68 // and this pool is full, the object is dropped. 69 GetOrNew() interface{} 70 } 71 72 // NewCircularPool creates a fixed-size rotating object pool. maxSize is the maximum 73 // number of objects in the pool, while initialSize controls how many objects are preallocated. 74 // Setting initialSize to 0 yields an initially empty pool. If maxSize < 1 or initialSize > maxSize, 75 // and error is returned. 76 // 77 // The new function is used to create objects for the pool. This function cannot be nil, or an 78 // error is returned, and must be safe for concurrent execution. If initialSize > 0, then 79 // the new function is used to preallocate objects for the pool. 80 // 81 // For an initially empty pool, be aware that Get will block forever. In that case, another 82 // goroutine must call Put in order to release the goroutine waiting on Get. Initially empty 83 // pools are appropriate as concurrent barriers, for example. 84 func NewCircularPool(initialSize, maxSize int, new func() interface{}) (CircularPool, error) { 85 if maxSize < 1 { 86 return nil, ErrorInvalidMaxSize 87 } else if initialSize > maxSize { 88 return nil, ErrorInvalidInitialSize 89 } else if new == nil { 90 return nil, ErrorNewRequired 91 } 92 93 pool := &circularPool{ 94 new: new, 95 objects: make(chan interface{}, maxSize), 96 } 97 98 for preallocate := 0; preallocate < initialSize; preallocate++ { 99 pool.objects <- pool.new() 100 } 101 102 return pool, nil 103 } 104 105 // circularPool is the internal implementation of CircularPool. It's based around a channel 106 // that stores the objects. 107 type circularPool struct { 108 new func() interface{} 109 objects chan interface{} 110 } 111 112 func (pool *circularPool) Get() interface{} { 113 return <-pool.objects 114 } 115 116 func (pool *circularPool) TryGet() (interface{}, bool) { 117 select { 118 case value := <-pool.objects: 119 return value, true 120 default: 121 return nil, false 122 } 123 } 124 125 func (pool *circularPool) GetCancel(done <-chan struct{}) (interface{}, bool) { 126 select { 127 case value := <-pool.objects: 128 return value, true 129 case <-done: 130 return nil, false 131 } 132 } 133 134 func (pool *circularPool) GetTimeout(timeout time.Duration) (interface{}, bool) { 135 if timeout < 1 { 136 return pool.TryGet() 137 } 138 139 timer := time.NewTimer(timeout) 140 141 // Stop() performs a tiny cleanup in the event 142 // that a value was obtained before the timer fired 143 defer timer.Stop() 144 145 select { 146 case value := <-pool.objects: 147 return value, true 148 case <-timer.C: 149 return nil, false 150 } 151 } 152 153 func (pool *circularPool) Put(value interface{}) { 154 select { 155 case pool.objects <- value: 156 default: 157 } 158 } 159 160 func (pool *circularPool) GetOrNew() interface{} { 161 select { 162 case value := <-pool.objects: 163 return value 164 default: 165 return pool.new() 166 } 167 }