{"record":{"id":"39cb9ac7a82d30c3","repo":"geektutu/7days-golang","slug":"connection-is-shut-down-39cb9a","errorCode":null,"errorMessage":"connection is shut down","messagePattern":"connection is shut down","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gee-rpc/day7-registry/client.go","lineNumber":55,"sourceCode":"// Client represents an RPC Client.\n// There may be multiple outstanding Calls associated\n// with a single Client, and a Client may be used by\n// multiple goroutines simultaneously.\ntype Client struct {\n\tcc       codec.Codec\n\topt      *Option\n\tsending  sync.Mutex // protect following\n\theader   codec.Header\n\tmu       sync.Mutex // protect following\n\tseq      uint64\n\tpending  map[uint64]*Call\n\tclosing  bool // user has called Close\n\tshutdown bool // server has told us to stop\n}\n\nvar _ io.Closer = (*Client)(nil)\n\nvar ErrShutdown = errors.New(\"connection is shut down\")\n\n// Close the connection\nfunc (client *Client) Close() error {\n\tclient.mu.Lock()\n\tdefer client.mu.Unlock()\n\tif client.closing {\n\t\treturn ErrShutdown\n\t}\n\tclient.closing = true\n\treturn client.cc.Close()\n}\n\n// IsAvailable return true if the client does work\nfunc (client *Client) IsAvailable() bool {\n\tclient.mu.Lock()\n\tdefer client.mu.Unlock()\n\treturn !client.shutdown && !client.closing\n}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/geektutu/7days-golang/blob/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-rpc/day7-registry/client.go#L37-L73","documentation":"ErrShutdown is the sentinel error returned when operating on a Client whose connection has been closed by the user (client.closing) or shut down by the server (client.shutdown). Calls made afterwards fail immediately instead of being sent.","triggerScenarios":"Calling client.Call/Go after client.Close(); using a client after receive() hit an error and terminateCalls set shutdown; or calling Close() twice in defer + explicit code paths.","commonSituations":"Connection dropped (server restart, network cut) so the client auto-shuts down and later calls fail; reusing a cached client past its lifetime; double-Close in defer handlers; load balancer dialing a dead server then reusing the failed client.","solutions":["Recreate the client (geeRPC.XDial / dialTimeout) after ErrShutdown and retry the request once.","Keep a single owner of the client lifecycle; call Close exactly once via defer.","Detect ErrShutdown (errors.Is / equality) and rebuild the connection rather than re-calling on the same client."],"exampleFix":"// before\nerr := client.Call(ctx, \"Foo.Sum\", req, reply) // ErrShutdown if connection died\n// after\nerr := client.Call(ctx, \"Foo.Sum\", req, reply)\nif err == geeRPC.ErrShutdown {\n    client, err = geeRPC.XDial(serverAddr)\n    if err == nil { err = client.Call(ctx, \"Foo.Sum\", req, reply) }\n}","handlingStrategy":"retry","validationCode":"client.mu.Lock()\nclosed := client.closing || client.shutdown\nclient.mu.Unlock()\nif closed { client, _ = geeRPC.XDial(addr) } // rebuild before calling","typeGuard":null,"tryCatchPattern":"if err := client.Call(ctx, \"Foo.Sum\", args, reply); err == geeRPC.ErrShutdown {\n    client, err = geeRPC.XDial(addr)\n    if err == nil { err = client.Call(ctx, \"Foo.Sum\", args, reply) }\n}","preventionTips":["Always create the client lazily and reconnect on ErrShutdown.","Call Close exactly once, via defer in the client's owner.","Treat any Call error on a long-lived client as a signal to check connection health."],"tags":["rpc","connection","shutdown","client"],"backgroundTag":"connection-shutdown","analyzedSha":"cf3644382101dc13e7fd92e8f5c66cabc51bcd3b","analyzedAt":"2026-09-03T18:31:24.087Z","contentChangedAt":"2026-09-03T18:31:24.087Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}