micro/go-micro · error

connection pool exhausted

Error message

connection pool exhausted

What it means

ErrPoolExhausted is the sentinel returned by the NATS connection pool when a caller requests a connection and all pooled connections are checked out (the internal connections channel is empty). Pool size is fixed at creation, so concurrency above that limit triggers this error.

Source

Thrown at transport/nats/pool.go:13

package nats

import (
	"errors"
	"sync"
	"time"

	natsp "github.com/nats-io/nats.go"
)

var (
	// ErrPoolExhausted is returned when no connections are available in the pool
	ErrPoolExhausted = errors.New("connection pool exhausted")
	// ErrPoolClosed is returned when trying to use a closed pool
	ErrPoolClosed = errors.New("connection pool is closed")
)

// connectionPool manages a pool of NATS connections
type connectionPool struct {
	mu          sync.RWMutex
	connections chan *pooledConnection
	factory     func() (*natsp.Conn, error)
	size        int
	idleTimeout time.Duration
	closed      bool
}

// pooledConnection wraps a NATS connection with metadata
type pooledConnection struct {
	conn      *natsp.Conn
	createdAt time.Time

View on GitHub (pinned to 24529f1404)

Solutions

  1. Increase the pool size at transport creation to cover peak concurrency
  2. Ensure every Get has a matching Put, including on error paths (defer put)
  3. Add retry/backoff around Get to ride out transient exhaustion
  4. Check for stuck/leaked connections holding the pool indefinitely

Example fix

// before
conn, _ := pool.Get(ctx) // fails under burst
// after
conn, err := pool.Get(ctx)
if errors.Is(err, nats.ErrPoolExhausted) {
	time.Sleep(50 * time.Millisecond)
	conn, err = pool.Get(ctx)
}
defer pool.Put(conn)
Defensive patterns

Strategy: retry

Validate before calling

// limit concurrency to pool size before calling Get
sem := make(chan struct{}, poolSize)
sem <- struct{}{}
defer func() { <-sem }()

Try / catch

conn, err := pool.Get(ctx)
if errors.Is(err, nats.ErrPoolExhausted) {
	select {
	case <-time.After(100 * time.Millisecond):
		conn, err = pool.Get(ctx)
	case <-ctx.Done():
		return ctx.Err()
	}
}

Prevention

When it happens

Trigger: Calling pool.Get() (or Dial when pooling enabled) while every connection in the fixed-size pool is currently borrowed and not yet returned via Put.

Common situations: Burst of concurrent requests exceeding pool size; leaked connections from code paths that Get without Put (error paths, panics); pool sized too small for service concurrency; slow operations holding connections long.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/f05fc391f255c33e. Report an issue: GitHub.