micro/go-micro · warning
unable to close in time
Error message
unable to close in time
What it means
pool.close in internal/util/pool/default.go closes a pooled client asynchronously and waits up to p.closeTimeout for the Close call to finish. If the timer fires before Close returns, the pool gives up and returns this error. It means the underlying client hung during shutdown.
Source
Thrown at internal/util/pool/default.go:154
return conn.(*poolConn).close()
}
p.conns[conn.Remote()] = append(conns, conn.(*poolConn))
return nil
}
func (p *poolConn) close() error {
ch := make(chan error)
go func() {
defer close(ch)
ch <- p.Client.Close()
}()
t := time.NewTimer(p.closeTimeout)
var err error
select {
case <-t.C:
err = errors.New("unable to close in time")
case err = <-ch:
t.Stop()
}
return err
}
View on GitHub (pinned to 24529f1404)
Solutions
- Increase the pool's closeTimeout option so slow-but-healthy clients have time to close.
- Ensure all in-flight calls/streaming RPCs are completed or cancelled before closing the pool.
- Check that the backend the client connects to is reachable and honoring connection close.
- Investigate the client's Close path for hangs (e.g. dial retries or un-drained streams) and fix the root cause.
Example fix
// before pool, err := pool.NewPool(pool.CloseTimeout(500*time.Millisecond)) // after pool, err := pool.NewPool(pool.CloseTimeout(5*time.Second))
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
if err := p.Close(); err != nil {
if strings.Contains(err.Error(), "unable to close in time") {
log.Warn("pool close timed out; client may still be draining", "timeout", p.closeTimeout)
// proceed with shutdown; don't fail the whole process
return nil
}
return err
} Prevention
- Set a generous closeTimeout for production pools
- Cancel in-flight RPCs/streams before closing the pool
- Ensure backends are reachable and respond to connection close
- Monitor for hung Close calls and alert on repeated timeouts
When it happens
Trigger: Calling pool.Close() (or letting a pool be torn down) when an underlying gRPC/client connection cannot close promptly — e.g. in-flight RPCs, a blocked transport, or a server that never responds to connection teardown.
Common situations: Test cleanup where a fake server never closes connections; production shutdown with live streaming RPCs; network partitions making the client's close handshake block until TCP timeouts.
Related errors
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/13ba4eb1a28b9728.
Report an issue: GitHub.