thanos-io/thanos · error

ErrPoolExhausted

ErrPoolExhausted

Error message

pool exhausted

What it means

ErrPoolExhausted is returned by BucketedPool.Get when maxTotal is nonzero and allocating a slice of the requested size would push total used bytes over the cap. It is a backpressure signal: the pool is protecting memory by refusing the allocation, not an internal fault.

Solutions

  1. Ensure callers always return slices via Put so usedTotal is released
  2. Increase the pool's maxTotal limit or remove it (maxTotal = 0 means unlimited)
  3. Reduce concurrency or per-item sizes consuming the pool
  4. Handle the error at Get call sites with backpressure (retry after returning slices)

Example fix

// before
b, err := chunkPool.Get(600)
if err != nil { return err }
// after
b, err := chunkPool.Get(600)
if errors.Is(err, pool.ErrPoolExhausted) {
    return ErrBackpressure // drop/retry; pool memory cap reached
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check headroom before a large Get
if chunkPool.Capacity() != 0 { /* if a capacity accessor exists, compare against sz */ }

Try / catch

b, err := chunkPool.Get(sz)
if errors.Is(err, pool.ErrPoolExhausted) {
    // backpressure: shed load or retry after Put releases memory
    return ErrBusy
}

Prevention

When it happens

Trigger: BucketedPool.Get(sz) with p.maxTotal > 0 and usedTotal + sz > maxTotal, e.g. chunkPool.Get(600) when the pool's remaining capacity is below 600.

Common situations: High concurrent ingest exceeding configured chunk pool memory limit; leak of Put() calls (forgotten returns) inflating usedTotal; undersized pool for workload.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/3f4b0a50cc4a73aa. Report an issue: GitHub.

Appendix: source

Thrown at pkg/pool/pool.go:86

	var sizes []int

	for s := minSize; s <= maxSize; s = int(float64(s) * factor) {
		sizes = append(sizes, s)
	}
	p := &BucketedPool[T]{
		buckets:  make([]sync.Pool, len(sizes)),
		sizes:    sizes,
		maxTotal: maxTotal,
		new: func(sz int) *[]T {
			s := make([]T, 0, sz)
			return &s
		},
	}
	return p, nil
}

// ErrPoolExhausted is returned if a pool cannot provide the requested slice.
var ErrPoolExhausted = errors.New("pool exhausted")

// Get returns a slice into from the bucket that fits the given size.
func (p *BucketedPool[T]) Get(sz int) (*[]T, error) {
	p.mtx.Lock()
	defer p.mtx.Unlock()

	if p.maxTotal > 0 && p.usedTotal+uint64(sz) > p.maxTotal {
		return nil, ErrPoolExhausted
	}

	for i, bktSize := range p.sizes {
		if sz > bktSize {
			continue
		}
		b, ok := p.buckets[i].Get().(*[]T)
		if !ok {
			b = p.new(bktSize)
		}

View on GitHub (pinned to 35b8b99117)