{"record":{"id":"d00747f148a67b2e","repo":"vitessio/vitess","slug":"grpcvtctldclient-in-a-shutdown-state","errorCode":null,"errorMessage":"gRPCVtctldClient in a SHUTDOWN state","messagePattern":"gRPCVtctldClient in a SHUTDOWN state","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtctl/grpcvtctldclient/client.go","lineNumber":37,"sourceCode":"package grpcvtctldclient\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/connectivity\"\n\n\t\"vitess.io/vitess/go/vt/grpcclient\"\n\t\"vitess.io/vitess/go/vt/vtctl/grpcclientcommon\"\n\t\"vitess.io/vitess/go/vt/vtctl/vtctldclient\"\n\n\tvtctlservicepb \"vitess.io/vitess/go/vt/proto/vtctlservice\"\n)\n\nvar (\n\tErrConnectionShutdown = errors.New(\"gRPCVtctldClient in a SHUTDOWN state\")\n\tErrConnectionTimeout  = errors.New(\"gRPC connection wait time exceeded\")\n)\n\nconst connClosedMsg = \"grpc: the client connection is closed\"\n\ntype gRPCVtctldClient struct {\n\tcc *grpc.ClientConn\n\tc  vtctlservicepb.VtctldClient\n}\n\n//go:generate -command grpcvtctldclient go run ../vtctldclient/codegen\n//go:generate grpcvtctldclient --out client_gen.go\n\nfunc gRPCVtctldClientFactory(ctx context.Context, addr string) (vtctldclient.VtctldClient, error) {\n\topt, err := grpcclientcommon.SecureDialOption()\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtctl/grpcvtctldclient/client.go#L19-L55","documentation":"gRPCVtctldClient exposes ErrConnectionShutdown, a sentinel error returned by WaitForReady when the client is in a SHUTDOWN state — i.e., Close was called and the underlying gRPC connection can no longer be used for RPCs. The wrapper also detects the raw 'grpc: the client connection is closed' message and maps it to this sentinel.","triggerScenarios":"Calling WaitForReady (or any vtctldclient RPC backed by it) after Close() was invoked on the client; using a client whose Close was called by a deferred cleanup or by a previous shutdown sequence in the same process.","commonSituations":"Reusing a vtctldclient created in a setup function whose defer Close already ran; calling RPCs during vtctld shutdown/race with Close; long-lived processes that Close the client on one code path and later call it again.","solutions":["Create a new vtctldclient instance via vtctldclient.New instead of reusing a closed one","Fix control flow so Close is the last operation on the client (no RPCs after deferred Close)","Guard concurrent use of the client with synchronization or per-goroutine clients","Check errors.Is(err, grpcvtctldclient.ErrConnectionShutdown) to distinguish this from network failures"],"exampleFix":"// before\nclient, _ := vtctldclient.New(...)\ndefer client.Close()\n// ... client reused later in another function -> ErrConnectionShutdown\n// after\nclient, _ := vtctldclient.New(...)\nif err := client.WaitForReady(ctx); err != nil {\n    if errors.Is(err, grpcvtctldclient.ErrConnectionShutdown) {\n        client, err = vtctldclient.New(...) // recreate and retry\n    }\n}","handlingStrategy":"try-catch","validationCode":"if client == nil || isClosed(client) {\n    client, err = vtctldclient.New(addr)\n    if err != nil {\n        return err\n    }\n}","typeGuard":"func isConnectionShutdownErr(err error) bool {\n    return errors.Is(err, grpcvtctldclient.ErrConnectionShutdown) ||\n        strings.Contains(err.Error(), \"grpc: the client connection is closed\")\n}","tryCatchPattern":"if err := client.WaitForReady(ctx); err != nil {\n    if errors.Is(err, grpcvtctldclient.ErrConnectionShutdown) {\n        client, err = vtctldclient.New(addr)\n        if err != nil {\n            return err\n        }\n        return client.WaitForReady(ctx)\n    }\n    return err\n}","preventionTips":["Never issue RPCs after client.Close(); make Close the final call","Do not share one vtctldclient across goroutines without synchronization","Avoid deferring Close in setup functions if the client outlives them","Check for ErrConnectionShutdown with errors.Is to recreate the client"],"tags":["grpc","connection-lifecycle","vtctldclient","client-shutdown"],"backgroundTag":"connection-closed","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}