{"record":{"id":"3bdcb64b5a27a02e","repo":"googleapis/mcp-toolbox","slug":"unable-to-initialize-source-q-source-is-closed","errorCode":null,"errorMessage":"unable to initialize source %q: source is closed","messagePattern":"unable to initialize source %q: source is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sources/connect.go","lineNumber":166,"sourceCode":"\t\treturn fmt.Errorf(\"unable to close source %q: %w\", c.name, err)\n\t}\n\treturn nil\n}\n\n// Do returns the connection, making it on the first call. Concurrent callers\n// share one attempt, and a failed attempt is not remembered.\n//\n// Because a failure is retried by the next caller rather than cached, connect\n// must release whatever it had already built before it returns an error. A\n// pool that fails its ping and is returned unclosed leaks once per tool call,\n// not once per process.\nfunc (c *ConnectOnce[T]) Do(ctx context.Context, connect func(context.Context) (T, error)) (T, error) {\n\tvar zero T\n\tif value, ok := c.Get(); ok {\n\t\treturn value, nil\n\t}\n\tif c.isClosed() {\n\t\treturn zero, fmt.Errorf(\"unable to initialize source %q: source is closed\", c.name)\n\t}\n\n\tch := c.initGroup.DoChan(\"\", func() (any, error) {\n\t\t// singleflight only shares an attempt that is still in flight, so a\n\t\t// caller queued behind a finished winner would start a second connect.\n\t\tif value, ok := c.Get(); ok {\n\t\t\treturn value, nil\n\t\t}\n\t\tif c.isClosed() {\n\t\t\treturn nil, fmt.Errorf(\"unable to initialize source %q: source is closed\", c.name)\n\t\t}\n\n\t\t// The attempt is shared by every waiter and the handle outlives the\n\t\t// request that triggered it, so it runs under the startup context\n\t\t// rather than the caller's: a request context carries that caller's\n\t\t// auth claims and a user agent that omits --user-agent-metadata, and it\n\t\t// is cancelled when that one caller goes away. Only the span context\n\t\t// crosses over, so the connect still appears in the trace of the","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/sources/connect.go#L148-L184","documentation":"Returned by ConnectOnce.Do when the source has already been closed (c.closed set by Close) and a caller still tries to lazily initialize the connection. The library refuses to start a new connect attempt on a closed source, wrapping the error with the source name. This is a lifecycle misuse: use-after-close.","triggerScenarios":"A request arrives after the server (or source manager) has begun shutting down and calls Do on a ConnectOnce whose Close already ran; Get() returns no value and isClosed() is true, so this error is returned immediately without any connect attempt.","commonSituations":"Requests in flight during graceful shutdown, load balancers still routing to a draining instance, or application code holding a source reference and invoking it after explicit Close.","solutions":["Stop routing traffic before/while closing sources: drain connections (readiness probe fail, SIGTERM handling) before calling Close","Check server shutdown ordering so request handlers finish before sources are closed","If triggered by LB draining, set a proper termination grace period and preStop hook","Audit application code for calls to the source after explicit Close"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"val, err := source.Do(ctx, connect)\nif err != nil && strings.Contains(err.Error(), \"source is closed\") {\n\t// fail fast; do not hammer — retry against another instance or after restart\n\treturn nil, status.Errorf(codes.Unavailable, \"source is shutting down: %v\", err)\n}","preventionTips":["Drain traffic (readiness=fail, LB deregister) before closing sources","Use graceful shutdown that waits for in-flight requests before Close","Set adequate terminationGracePeriodSeconds in Kubernetes","Avoid holding source references past explicit Close in app code"],"tags":["lifecycle","shutdown","use-after-close","go"],"backgroundTag":"source-already-closed","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}