geektutu/7days-golang · error · ErrShutdown

connection is shut down

Error message

connection is shut down

What it means

ErrShutdown is the sentinel error returned when an RPC call is attempted on a Client whose connection is no longer usable: either the user called Close (closing=true) or the server told the client to stop (shutdown=true). registerCall refuses to add new pending calls and returns this error, so in-flight calls are also terminated with it.

Source

Thrown at gee-rpc/day5-http-debug/client.go:55

// 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

  1. Detect errors.Is(err, geerpc.ErrShutdown) (or string match) and redial a fresh client before retrying the call
  2. Stop issuing calls after Close and coordinate shutdown with context cancellation/WaitGroup
  3. Add connection health checks or automatic reconnect logic in the client wrapper
  4. Investigate server-side shutdown/disconnects if ErrShutdown appears without an explicit Close

Example fix

// before
client.Close()
err := client.Call(ctx, "Foo.Sum", args, reply) // ErrShutdown

// after
if err := client.Call(ctx, "Foo.Sum", args, reply); errors.Is(err, geerpc.ErrShutdown) {
	client = dialNewClient() // redial
	err = client.Call(ctx, "Foo.Sum", args, reply)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check client usability before calling
func (c *ClientPool) Get() (*geerpc.Client, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.client == nil || c.closed {
		return c.redial()
	}
	return c.client, nil
}

Try / catch

err := client.Call(ctx, "Foo.Sum", args, reply)
if err != nil && strings.Contains(err.Error(), "connection is shut down") {
	client = redial(addr) // recreate client and retry once
	err = client.Call(ctx, "Foo.Sum", args, reply)
}

Prevention

When it happens

Trigger: Calling Call/Go after client.Close(); server sent a shutdown signal or the connection was torn down, terminating all pending calls; reusing a client that another goroutine already closed.

Common situations: Long-lived client cached in a struct but closed during application shutdown while other goroutines still use it; server restart dropping the connection; load-balancer idle timeout killing the connection; race between shutdown and request handling.

Related errors


AI-assisted analysis of geektutu/7days-golang@cf36443821 (2026-09-03). Data as JSON: /api/errors/aa26cd483e08a7eb. Report an issue: GitHub.