{"record":{"id":"71941429c67d7b53","repo":"grpc/grpc-go","slug":"grpc-the-connection-is-drained","errorCode":null,"errorMessage":"grpc: the connection is drained","messagePattern":"grpc: the connection is drained","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clientconn.go","lineNumber":72,"sourceCode":"\t_ \"google.golang.org/grpc/internal/resolver/passthrough\" // To register passthrough resolver.\n\t_ \"google.golang.org/grpc/internal/resolver/unix\"        // To register unix resolver.\n\t_ \"google.golang.org/grpc/resolver/dns\"                  // To register dns resolver.\n)\n\nconst (\n\t// minimum time to give a connection to complete\n\tminConnectTimeout = 20 * time.Second\n)\n\nvar (\n\t// ErrClientConnClosing indicates that the operation is illegal because\n\t// the ClientConn is closing.\n\t//\n\t// Deprecated: this error should not be relied upon by users; use the status\n\t// code of Canceled instead.\n\tErrClientConnClosing = status.Error(codes.Canceled, \"grpc: the client connection is closing\")\n\t// errConnDrain indicates that the connection starts to be drained and does not accept any new RPCs.\n\terrConnDrain = errors.New(\"grpc: the connection is drained\")\n\t// errConnClosing indicates that the connection is closing.\n\terrConnClosing = errors.New(\"grpc: the connection is closing\")\n\t// errConnIdling indicates the connection is being closed as the channel\n\t// is moving to an idle mode due to inactivity.\n\terrConnIdling = errors.New(\"grpc: the connection is closing due to channel idleness\")\n\t// invalidDefaultServiceConfigErrPrefix is used to prefix the json parsing error for the default\n\t// service config.\n\tinvalidDefaultServiceConfigErrPrefix = \"grpc: the provided default service config is invalid\"\n\t// PickFirstBalancerName is the name of the pick_first balancer.\n\tPickFirstBalancerName = pickfirst.Name\n)\n\n// The following errors are returned from Dial and DialContext\nvar (\n\t// errNoTransportSecurity indicates that there is no transport security\n\t// being set for ClientConn. Users should either set one or explicitly\n\t// call WithInsecure DialOption to disable security.\n\terrNoTransportSecurity = errors.New(\"grpc: no transport security set (use grpc.WithTransportCredentials(insecure.NewCredentials()) explicitly or set credentials)\")","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/clientconn.go#L54-L90","documentation":"Internal sentinel errConnDrain (clientconn.go:71-72) meaning the channel is draining and no longer accepts new RPCs. It is passed to addrConn.tearDown when a SubConn is shut down because the balancer removed its address (balancer_wrapper.go:355 removeAddrConn(... , errConnDrain)). On tearDown, if err==errConnDrain the transport is GracefulClose'd (clientconn.go:1713) so in-flight streams finish.","triggerScenarios":"A SubConn is removed by the balancer (address list shrank, resolver dropped it, server sent GOAWAY causing address removal) and that SubConn's transport is torn down with errConnDrain. New RPCs that try to use it get this error.","commonSituations":"Resolver address list changed during a rolling deploy; server sent GOAWAY; xDS removed a locality/endpoint; backend scaled down; in-flight RPC issued against a SubConn the instant it was removed.","solutions":["Retry the RPC on a fresh pick — the balancer will route to a remaining READY SubConn; for idempotent calls wrap with a retry interceptor.","Verify the resolver/backend set still contains healthy addresses; if all are draining, wait for new ones or fail over.","Do not hold references to a specific SubConn across long-lived logic; rely on the picker instead.","Enable the retry policy in the service config for the affected method so transient drain failures are retried transparently."],"exampleFix":"// before — single attempt on a draining backend\nresp, err := client.Get(ctx, req)\n\n// after — enable retry in service config (methodConfig retryPolicy)\n{\n  \"methodConfig\": [{\n    \"name\": [{\"service\": \"pkg.Svc\", \"method\": \"Get\"}],\n    \"retryPolicy\": {\n      \"maxAttempts\": 4,\n      \"initialBackoff\": \"0.1s\",\n      \"maxBackoff\": \"1s\",\n      \"backoffMultiplier\": 2,\n      \"retryableStatusCodes\": [\"UNAVAILABLE\"]\n    }\n  }]\n}","handlingStrategy":"retry","validationCode":"// Avoid issuing RPCs against a draining subchannel; rely on the picker\n// (No direct subchannel liveness check is exposed — instead use retry policy.)","typeGuard":null,"tryCatchPattern":"if errors.Is(err, errConnDrain) || status.Code(err) == codes.Unavailable {\n    // retry; balancer will pick another READY subchannel\n}","preventionTips":["Enable retry on UNAVAILABLE for idempotent methods.","Do not cache a specific SubConn in application code.","Expect drain noise during deploys; classify it as transient."],"tags":["connection","drain","goaway","grpc-go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}