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  }