{"record":{"id":"f051fd0005d4d222","repo":"geektutu/7days-golang","slug":"connection-is-shut-down","errorCode":null,"errorMessage":"connection is shut down","messagePattern":"connection is shut down","errorType":"exception","errorClass":"ErrShutdown","httpStatus":null,"severity":"error","filePath":"gee-rpc/day2-client/client.go","lineNumber":50,"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":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/geektutu/7days-golang/blob/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-rpc/day2-client/client.go#L32-L68","documentation":"ErrShutdown is the gee-rpc client's sentinel (errors.New(\"connection is shut down\")). Close() and registerCall return it when the client has been locally closed (closing=true) or told to stop by the server (shutdown=true); all subsequent calls on that client fail.","triggerScenarios":"Calling a method after client.Close(); server sent a shutdown notice and the client calls registerCall for new requests; using a client shared across goroutines after another goroutine closed it.","commonSituations":"Closing a pooled client while requests are still being submitted; connection idle-timeout on the server marks shutdown; lifecycle bug where Close runs before in-flight work finishes.","solutions":["Create a new client (re-dial) after ErrShutdown instead of reusing the old one","Order lifecycle so Close happens only after all requests complete","Check client.closing/shutdown state before submitting work in concurrent code","Detect the sentinel with errors.Is(err, ErrShutdown) and reconnect transparently"],"exampleFix":"// before\ncall := <-client.Go(\"Foo.Sum\", args, reply, nil).Done // after Close -> ErrShutdown\n// after\nif err := client.Call(\"Foo.Sum\", args, reply); errors.Is(err, rpc.ErrShutdown) {\n    client = rpc.Dial(\"tcp\", addr) // reconnect\n    err = client.Call(\"Foo.Sum\", args, reply)\n}","handlingStrategy":"try-catch","validationCode":"func clientAlive(c *rpc.Client) bool {\n    c.mu.Lock(); defer c.mu.Unlock()\n    return !c.closing && !c.shutdown\n}","typeGuard":null,"tryCatchPattern":"if err := client.Call(\"Svc.Method\", args, reply); err != nil {\n    if errors.Is(err, ErrShutdown) {\n        client, err = Dial(network, addr) // reconnect and retry once\n        if err == nil { err = client.Call(\"Svc.Method\", args, reply) }\n    }\n}","preventionTips":["Never share a client after Close across goroutines","Use a reconnecting wrapper/pool around Dial","Check for server idle-timeout config","Sequence Close after all pending calls complete"],"tags":["rpc","network","connection-lifecycle"],"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"}