googleapis/mcp-toolbox · warning
unable to close source %q: %w
Error message
unable to close source %q: %w
What it means
Returned by ConnectOnce.Close when releasing an already-initialized source connection fails. Close only attempts release if a connection is ready; if c.release(ctx, value) errors, the source's name and the underlying teardown error are wrapped. This indicates the resource cleanup (e.g., closing a DB pool) itself failed, not the shutdown flow.
Source
Thrown at internal/sources/connect.go:148
// Close does not wait for an attempt that is already in flight. That attempt
// releases its own result rather than caching it into a closed holder, so
// shutdown never blocks on a connect that may be hung for the full timeout.
func (c *ConnectOnce[T]) Close(ctx context.Context) error {
c.mu.Lock()
if c.closed {
c.mu.Unlock()
return nil
}
var zero T
value, ready := c.value, c.ready
c.value, c.ready, c.closed = zero, false, true
c.mu.Unlock()
if !ready {
return nil
}
if err := c.release(ctx, value); err != nil {
return fmt.Errorf("unable to close source %q: %w", c.name, err)
}
return nil
}
// Do returns the connection, making it on the first call. Concurrent callers
// share one attempt, and a failed attempt is not remembered.
//
// Because a failure is retried by the next caller rather than cached, connect
// must release whatever it had already built before it returns an error. A
// pool that fails its ping and is returned unclosed leaks once per tool call,
// not once per process.
func (c *ConnectOnce[T]) Do(ctx context.Context, connect func(context.Context) (T, error)) (T, error) {
var zero T
if value, ok := c.Get(); ok {
return value, nil
}
if c.isClosed() {
return zero, fmt.Errorf("unable to initialize source %q: source is closed", c.name)View on GitHub (pinned to 8cc6e09de2)
Solutions
- Inspect the wrapped cause (%w) to identify which underlying close operation failed
- Check network connectivity to the database if release involves waiting for connections to drain
- If this appears only at shutdown, it is usually benign — the process is exiting anyway; log and continue
- Update drivers/SDK versions if the wrapped error points at a known driver teardown bug
Defensive patterns
Strategy: try-catch
Try / catch
// Wrap source Close and log rather than crash at shutdown
if err := source.Close(ctx); err != nil {
var closedErr *fmt.wrapError // inspect wrapped cause
log.Printf("warning: source cleanup failed during shutdown: %v", err)
// non-fatal at shutdown unless cleanup is contractually required
} Prevention
- Treat close-time errors as warnings during process exit
- Ensure the database is reachable during drain if release needs cooperation
- Check wrapped error text to distinguish driver teardown bugs from network loss
- Keep driver/SDK versions current
When it happens
Trigger: Server shutdown calls Close on a source whose connection was successfully created, but c.release returns an error while closing the underlying resource (e.g., pool.Close() or driver Close failing, or the release function's context being cancelled/expired).
Common situations: Database already severed mid-shutdown so pool close hangs or errors, driver-level failures during teardown, or a context deadline exceeded while draining connections at process exit.
Related errors
- unable to initialize source %q: source is closed
- error shutting down OpenTelemetry: %w
- error finding YAML files in %q: %w
- error finding YML files in %q: %w
- failed to initialize resources: %w
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/a81a4f4daf4bceb9.
Report an issue: GitHub.