geektutu/7days-golang · error · ErrShutdown

connection is shut down

Error message

connection is shut down

What it means

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.

Source

Thrown at gee-rpc/day6-load-balance/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. Track client lifecycle: only call Close() once (e.g. sync.Once) and never issue calls after it
  2. Create a new client via Dial/DialHTTP/DialTimeout when ErrShutdown is returned
  3. Wrap client access in a connection pool that discards and reconnects on ErrShutdown
  4. Check server logs/health: a shutdown flag means the server told the client to stop

Example fix

// before
client.Close()
...
client.Close() // second call returns ErrShutdown

// after
var closeOnce sync.Once
closeOnce.Do(func() { client.Close() })
if err := client.Call(ctx, "Foo.Bar", args, reply); errors.Is(err, ErrShutdown) {
    client, _ = DialTimeout("tcp", addr, 3*time.Second)
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := client.Call(ctx, "Foo.Bar", args, reply); err != nil {
    if errors.Is(err, ErrShutdown) {
        client, err = DialTimeout("tcp", addr, 3*time.Second) // reconnect
    }
}

Prevention

When it happens

Trigger: 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().

Common situations: 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.

Related errors


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