{"record":{"id":"41681a32faa43357","repo":"geektutu/7days-golang","slug":"connection-is-shut-down-41681a","errorCode":null,"errorMessage":"connection is shut down","messagePattern":"connection is shut down","errorType":"exception","errorClass":"ErrShutdown","httpStatus":null,"severity":"error","filePath":"gee-rpc/day6-load-balance/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/day6-load-balance/client.go#L37-L73","documentation":"ErrShutdown indicates the Client connection is no longer usable because it has been closed by the user (closing flag) or told to shut down by the server (shutdown flag). Close() returns it when called twice, and registerCall returns it for new calls on a closed client. It guards against using a dead connection.","triggerScenarios":"Calling Close() on a Client that is already closed; calling Call/Go after Close() or after the server sent a shutdown; calling on a Client whose connection failed during receive().","commonSituations":"Double-defer of client.Close(); reusing a cached client after the server restarted; long-lived clients hit by server-side idle timeouts then used again.","solutions":["Track client lifecycle: only call Close() once (e.g. sync.Once) and never issue calls after it","Create a new client via Dial/DialHTTP/DialTimeout when ErrShutdown is returned","Wrap client access in a connection pool that discards and reconnects on ErrShutdown","Check server logs/health: a shutdown flag means the server told the client to stop"],"exampleFix":"// before\nclient.Close()\n...\nclient.Close() // second call returns ErrShutdown\n\n// after\nvar closeOnce sync.Once\ncloseOnce.Do(func() { client.Close() })\nif err := client.Call(ctx, \"Foo.Bar\", args, reply); errors.Is(err, ErrShutdown) {\n    client, _ = DialTimeout(\"tcp\", addr, 3*time.Second)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := client.Call(ctx, \"Foo.Bar\", args, reply); err != nil {\n    if errors.Is(err, ErrShutdown) {\n        client, err = DialTimeout(\"tcp\", addr, 3*time.Second) // reconnect\n    }\n}","preventionTips":["Close clients with sync.Once to avoid double-Close returning ErrShutdown","Treat Client as single-use-after-close; rebuild instead of reusing","Wrap clients in a pool that replaces instances on ErrShutdown","Monitor server restarts that flip the shutdown flag on connected clients"],"tags":["rpc","connection-lifecycle","go","shutdown"],"backgroundTag":"connection-shut-down","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"}