grpc/grpc-go · error

%v: %v

Error message

%v: %v

What it means

Inside DialContext's blocking wait loop, when the context expires while a connection error also exists and WithReturnConnectionError (returnLastError) is set, grpc-go combines both into "<context error>: <connection error>" (clientconn.go:325-326). This gives the caller both the deadline/timeout reason and the underlying transport failure.

Solutions

  1. Read both halves of the combined error: the context side (deadline exceeded / canceled) and the connection side (the real transport failure).
  2. Address the connection error (e.g., check host/port, TLS config, network reachability) and/or increase the context timeout.
  3. Drop WithBlock() if you do not need to block on READY; use grpc.NewClient and handle RPC errors instead.

Example fix

// before
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
cc, err := grpc.DialContext(ctx, addr, grpc.WithBlock(), grpc.WithReturnConnectionError())
// after: give more time and verify reachability
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cc, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
Defensive patterns

Strategy: try-catch

Try / catch

cc, err := grpc.DialContext(ctx, target, grpc.WithBlock(), grpc.WithReturnConnectionError())
if err != nil {
    // err may be "<ctx error>: <conn error>"
    if errors.Is(err, context.DeadlineExceeded) { /* timeout */ }
    var connErr = ccErr // capture connectionError via separate path if needed
    _ = connErr
}

Prevention

When it happens

Trigger: grpc.Dial/DialContext is called with WithBlock(), WithReturnConnectionError(), and a context that times out before the channel reaches READY, while a connection error was recorded (e.g., all subchannels failed).

Common situations: Short dial timeouts against an unreachable backend; TLS handshake failures combined with a tight context deadline; wrong port or firewall dropping packets so the deadline fires first.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/136bb3b8fd1e382e. Report an issue: GitHub.

Appendix: source

Thrown at clientconn.go:326

	if !cc.dopts.block {
		return cc, nil
	}

	if cc.dopts.timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout)
		defer cancel()
	}
	defer func() {
		select {
		case <-ctx.Done():
			switch {
			case ctx.Err() == err:
				conn = nil
			case err == nil || !cc.dopts.returnLastError:
				conn, err = nil, ctx.Err()
			default:
				conn, err = nil, fmt.Errorf("%v: %v", ctx.Err(), err)
			}
		default:
		}
	}()

	// A blocking dial blocks until the clientConn is ready.
	for {
		s := cc.GetState()
		if s == connectivity.Idle {
			cc.Connect()
		}
		if s == connectivity.Ready {
			return cc, nil
		} else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {
			if err = cc.connectionError(); err != nil {
				terr, ok := err.(interface {
					Temporary() bool
				})

View on GitHub (pinned to 0c51461d27)