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.MutexView on GitHub (pinned to 24529f1404)
Solutions
- Serialize shutdown: stop all workers before calling pool.Close()
- Re-create the transport/pool if it was closed but is still needed
- Guard call sites with a state check or errors.Is(err, ErrPoolClosed) fallback to re-dial
- 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
- Close pools only after all workers have stopped (use WaitGroup)
- Track pool lifecycle with a single owner; avoid double Close
- On ErrPoolClosed, recreate the pool rather than retrying on the same one
- Coordinate shutdown via context cancellation before closing
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
- ErrPoolClosed
- invalid connection from pool
- ErrPoolExhausted
- invalid connection from pool
- connection pool exhausted
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/6feca8a67c210814.
Report an issue: GitHub.