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
- Ensure callers always return slices via Put so usedTotal is released
- Increase the pool's maxTotal limit or remove it (maxTotal = 0 means unlimited)
- Reduce concurrency or per-item sizes consuming the pool
- 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
- Always call Put for every Get (use defer for short-lived slices)
- Size maxTotal to peak concurrent ingest, not average
- Set maxTotal = 0 if unbounded allocation is acceptable
- Monitor pool usage metrics and alert near the cap
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)