github.com/thanos-io/thanos@v0.32.5/pkg/pool/pool.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package pool 5 6 import ( 7 "sync" 8 9 "github.com/pkg/errors" 10 ) 11 12 // Bytes is a pool of bytes that can be reused. 13 type Bytes interface { 14 // Get returns a new byte slices that fits the given size. 15 Get(sz int) (*[]byte, error) 16 // Put returns a byte slice to the right bucket in the pool. 17 Put(b *[]byte) 18 } 19 20 // NoopBytes is pool that always allocated required slice on heap and ignore puts. 21 type NoopBytes struct{} 22 23 func (p NoopBytes) Get(sz int) (*[]byte, error) { 24 b := make([]byte, 0, sz) 25 return &b, nil 26 } 27 28 func (p NoopBytes) Put(*[]byte) {} 29 30 // BucketedBytes is a bucketed pool for variably sized byte slices. It can be configured to not allow 31 // more than a maximum number of bytes being used at a given time. 32 // Every byte slice obtained from the pool must be returned. 33 type BucketedBytes struct { 34 buckets []sync.Pool 35 sizes []int 36 maxTotal uint64 37 usedTotal uint64 38 mtx sync.Mutex 39 40 new func(s int) *[]byte 41 } 42 43 // MustNewBucketedBytes is like NewBucketedBytes but panics if construction fails. 44 // Useful for package internal pools. 45 func MustNewBucketedBytes(minSize, maxSize int, factor float64, maxTotal uint64) *BucketedBytes { 46 pool, err := NewBucketedBytes(minSize, maxSize, factor, maxTotal) 47 if err != nil { 48 panic(err) 49 } 50 return pool 51 } 52 53 // NewBucketedBytes returns a new Bytes with size buckets for minSize to maxSize 54 // increasing by the given factor and maximum number of used bytes. 55 // No more than maxTotal bytes can be used at any given time unless maxTotal is set to 0. 56 func NewBucketedBytes(minSize, maxSize int, factor float64, maxTotal uint64) (*BucketedBytes, error) { 57 if minSize < 1 { 58 return nil, errors.New("invalid minimum pool size") 59 } 60 if maxSize < 1 { 61 return nil, errors.New("invalid maximum pool size") 62 } 63 if factor < 1 { 64 return nil, errors.New("invalid factor") 65 } 66 67 var sizes []int 68 69 for s := minSize; s <= maxSize; s = int(float64(s) * factor) { 70 sizes = append(sizes, s) 71 } 72 p := &BucketedBytes{ 73 buckets: make([]sync.Pool, len(sizes)), 74 sizes: sizes, 75 maxTotal: maxTotal, 76 new: func(sz int) *[]byte { 77 s := make([]byte, 0, sz) 78 return &s 79 }, 80 } 81 return p, nil 82 } 83 84 // ErrPoolExhausted is returned if a pool cannot provide the request bytes. 85 var ErrPoolExhausted = errors.New("pool exhausted") 86 87 // Get returns a new byte slice that fits the given size. 88 func (p *BucketedBytes) Get(sz int) (*[]byte, error) { 89 p.mtx.Lock() 90 defer p.mtx.Unlock() 91 92 if p.maxTotal > 0 && p.usedTotal+uint64(sz) > p.maxTotal { 93 return nil, ErrPoolExhausted 94 } 95 96 for i, bktSize := range p.sizes { 97 if sz > bktSize { 98 continue 99 } 100 b, ok := p.buckets[i].Get().(*[]byte) 101 if !ok { 102 b = p.new(bktSize) 103 } 104 105 p.usedTotal += uint64(cap(*b)) 106 return b, nil 107 } 108 109 // The requested size exceeds that of our highest bucket, allocate it directly. 110 p.usedTotal += uint64(sz) 111 return p.new(sz), nil 112 } 113 114 // Put returns a byte slice to the right bucket in the pool. 115 func (p *BucketedBytes) Put(b *[]byte) { 116 if b == nil { 117 return 118 } 119 120 sz := cap(*b) 121 for i, bktSize := range p.sizes { 122 if sz > bktSize { 123 continue 124 } 125 *b = (*b)[:0] 126 p.buckets[i].Put(b) 127 break 128 } 129 130 p.mtx.Lock() 131 defer p.mtx.Unlock() 132 // We could assume here that our users will not make the slices larger 133 // but lets be on the safe side to avoid an underflow of p.usedTotal. 134 if uint64(sz) >= p.usedTotal { 135 p.usedTotal = 0 136 } else { 137 p.usedTotal -= uint64(sz) 138 } 139 }