grpc/grpc-go · error

%v: %v

Error message

%v: %v

What it means

In the blocking DialContext loop (clientconn.go:317-330), a deferred handler fires when the dial context expires or is cancelled. If there is BOTH a context error and a connection error (and returnLastError is not the chosen branch), it returns fmt.Errorf("%v: %v", ctx.Err(), err), combining e.g. a deadline-exceeded with the underlying connection failure.

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 03255a9237)

Solutions

  1. Increase the dial context timeout or remove WithBlock if you don't need a synchronous ready.
  2. Address the underlying connection error shown after the colon (TLS, DNS, refused connection).
  3. Switch to grpc.NewClient which is non-blocking and reports failures per-RPC instead of at dial time.

Example fix

// before
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
conn, err := grpc.DialContext(ctx, target, grpc.WithBlock())
// after
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
conn, err := grpc.DialContext(ctx, target, grpc.WithBlock())
Defensive patterns

Strategy: try-catch

Validate before calling

// Size the context timeout to realistic connection latency before a blocking dial.
func dialTimeout(target string) time.Duration {
    if strings.HasPrefix(target, "xds:///") { return 60 * time.Second }
    return 10 * time.Second
}

Try / catch

conn, err := grpc.DialContext(ctx, target, grpc.WithBlock())
if err != nil {
    // err may combine ctx.Err() and connection error; inspect both
    log.Printf("dial failed: %v (ctx: %v)", err, ctx.Err())
    return nil, err
}

Prevention

When it happens

Trigger: Using grpc.DialContext with WithBlock(true) and a context that times out while the connection is also failing. The combined message surfaces when the context, not the transport, is the proximate cause.

Common situations: A short dial timeout against an unreachable backend; DNS resolution slower than the context deadline; the server rejecting TLS while the context expires simultaneously.

Related errors


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