micro/go-micro · error

connection pool is closed

Error message

connection pool is closed

What it means

ErrPoolClosed is returned when Get (or TestConnectionPool-style checks) is called on a connection pool whose Close() has already run. The pool marks itself closed and drains its connection channel, so subsequent use is rejected instead of silently failing.

Source

Thrown at transport/nats/pool.go:15

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
	lastUsed  time.Time
	mu        sync.Mutex

View on GitHub (pinned to 24529f1404)

Solutions

  1. Serialize shutdown: stop all workers before calling pool.Close()
  2. Re-create the transport/pool if it was closed but is still needed
  3. Guard call sites with a state check or errors.Is(err, ErrPoolClosed) fallback to re-dial
  4. Avoid closing shared transports owned by another component

Example fix

// before
pool.Close()
conn, err := pool.Get(ctx) // ErrPoolClosed
// after
if err := pool.Close(); err != nil { _ = err }
// re-create pool or gate Get behind shutdown signal:
select {
case <-shutdown: return
default:
	conn, err = pool.Get(ctx)
}
Defensive patterns

Strategy: try-catch

Validate before calling

func poolUsable(closed atomic.Bool) bool { return !closed.Load() }
// if !poolUsable(poolClosed) { conn, err = pool.Get(ctx) } else { re-create pool }

Type guard

func isPoolClosed(err error) bool { return errors.Is(err, nats.ErrPoolClosed) }

Try / catch

conn, err := pool.Get(ctx)
if isPoolClosed(err) {
	pool, err = newPool() // recreate after close
	if err != nil { return err }
	conn, err = pool.Get(ctx)
}

Prevention

When it happens

Trigger: Calling pool.Get()/Dial after pool.Close(); using a transport whose shared pool was closed by another component; calling TestConnectionPool_Close flows where the pool is closed while still referenced; races between shutdown and in-flight requests.

Common situations: Graceful shutdown closing the pool while workers still process requests; double Close in cleanup; singleton transport closed by one code path while others keep dialing; tests closing a shared pool fixture early.

Related errors


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