grpc/grpc-go · error

last connection error: %v

Error message

last connection error: %v

What it means

Produced by baseBalancer.mergeErrors when the balancer is in TransientFailure, a SubConn connection error exists (connErr != nil) but there is no resolver error. This is the classic 'all backends unreachable' case: the resolver gave good addresses but every TCP/TLS handshake failed. The error is wrapped in the picker and returned as the RPC's Unavailable status.

Source

Thrown at balancer/base/balancer.go:156

		b.ResolverError(errors.New("produced zero addresses"))
		return balancer.ErrBadResolverState
	}

	b.regeneratePicker()
	b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker})
	return nil
}

// mergeErrors builds an error from the last connection error and the last
// resolver error.  Must only be called if b.state is TransientFailure.
func (b *baseBalancer) mergeErrors() error {
	// connErr must always be non-nil unless there are no SubConns, in which
	// case resolverErr must be non-nil.
	if b.connErr == nil {
		return fmt.Errorf("last resolver error: %v", b.resolverErr)
	}
	if b.resolverErr == nil {
		return fmt.Errorf("last connection error: %v", b.connErr)
	}
	return fmt.Errorf("last connection error: %v; last resolver error: %v", b.connErr, b.resolverErr)
}

// regeneratePicker takes a snapshot of the balancer, and generates a picker
// from it. The picker is
//   - errPicker if the balancer is in TransientFailure,
//   - built by the pickerBuilder with all READY SubConns otherwise.
func (b *baseBalancer) regeneratePicker() {
	if b.state == connectivity.TransientFailure {
		b.picker = NewErrPicker(b.mergeErrors())
		return
	}
	readySCs := make(map[balancer.SubConn]SubConnInfo)

	// Filter out all ready SCs from full subConn map.
	for addr, sc := range b.subConns.All() {
		if st, ok := b.scStates[sc]; ok && st == connectivity.Ready {

View on GitHub (pinned to 03255a9237)

Solutions

  1. Read the wrapped %v — it is the underlying connection error (e.g. dial tcp: connection refused, x509 cert error).
  2. Verify network reachability to the backend address/port from the client host.
  3. If TLS-related, check cert validity, SANs, and the client's credentials config.
  4. Restart/health-check the backends and confirm service discovery reflects them as healthy.

Example fix

// before: TLS handshake fails on all backends -> 'last connection error: ... x509: certificate signed by unknown authority'
conn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))   // no CA

// after:
creds := credentials.NewClientTLSFromCert(caPool, "")
conn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: dial the backend address to catch connection-level failures
// before the channel's SubConns all enter TransientFailure.
func checkDial(addr string, timeout time.Duration) error {
    conn, err := net.DialTimeout("tcp", addr, timeout)
    if err != nil { return err }
    conn.Close()
    return nil
}

Try / catch

// Treat as transient; retry with backoff for idempotent RPCs.
var lastErr error
for i := 0; i < maxRetries; i++ {
    lastErr = stub.Do(ctx, req)
    if lastErr == nil { return nil }
    if status.Code(lastErr) != codes.Unavailable { return lastErr }
    select { case <-time.After(backoffFor(i)): case <-ctx.Done(): return ctx.Err() }
}
return lastErr

Prevention

When it happens

Trigger: All SubConns under a base-balancer policy (round_robin, weighted_round_robin, etc.) have entered TransientFailure with a non-nil connection error, while the resolver itself succeeded. mergeErrors hits the `b.resolverErr == nil` branch.

Common situations: Backends are down or refusing connections; TLS certificate validation failure on every backend; firewall/security group blocking the gRPC port; wrong port in the resolved address; server crashed and endpoints haven't been re-registered.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/1abfb16b7e160a1b. Report an issue: GitHub.