geektutu/7days-golang · error · ErrShutdown
connection is shut down
Error message
connection is shut down
What it means
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.
Source
Thrown at gee-rpc/day2-client/client.go:50
// Client represents an RPC Client.
// There may be multiple outstanding Calls associated
// with a single Client, and a Client may be used by
// multiple goroutines simultaneously.
type Client struct {
cc codec.Codec
opt *Option
sending sync.Mutex // protect following
header codec.Header
mu sync.Mutex // protect following
seq uint64
pending map[uint64]*Call
closing bool // user has called Close
shutdown bool // server has told us to stop
}
var _ io.Closer = (*Client)(nil)
var ErrShutdown = errors.New("connection is shut down")
// Close the connection
func (client *Client) Close() error {
client.mu.Lock()
defer client.mu.Unlock()
if client.closing {
return ErrShutdown
}
client.closing = true
return client.cc.Close()
}
// IsAvailable return true if the client does work
func (client *Client) IsAvailable() bool {
client.mu.Lock()
defer client.mu.Unlock()
return !client.shutdown && !client.closing
}View on GitHub (pinned to cf36443821)
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
Example fix
// before
call := <-client.Go("Foo.Sum", args, reply, nil).Done // after Close -> ErrShutdown
// after
if err := client.Call("Foo.Sum", args, reply); errors.Is(err, rpc.ErrShutdown) {
client = rpc.Dial("tcp", addr) // reconnect
err = client.Call("Foo.Sum", args, reply)
} Defensive patterns
Strategy: try-catch
Validate before calling
func clientAlive(c *rpc.Client) bool {
c.mu.Lock(); defer c.mu.Unlock()
return !c.closing && !c.shutdown
} Try / catch
if err := client.Call("Svc.Method", args, reply); err != nil {
if errors.Is(err, ErrShutdown) {
client, err = Dial(network, addr) // reconnect and retry once
if err == nil { err = client.Call("Svc.Method", args, reply) }
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of geektutu/7days-golang@cf36443821 (2026-09-03).
Data as JSON: /api/errors/f051fd0005d4d222.
Report an issue: GitHub.