grpc/grpc-go · info

grpc: timed out when dialing

Error message

grpc: timed out when dialing

What it means

ErrClientConnTimeout (clientconn.go:1784-1789) is a DEPRECATED exported sentinel whose doc comment states plainly: 'This error is never returned by grpc and should not be referenced by users.' Modern grpc-go surfaces dial timeouts as context.DeadlineExceeded (from the dial context) wrapped in a status with code Unavailable.

Source

Thrown at clientconn.go:1789

func (ac *addrConn) incrCallsStarted() {
	ac.channelz.ChannelMetrics.CallsStarted.Add(1)
	ac.channelz.ChannelMetrics.LastCallStartedTimestamp.Store(time.Now().UnixNano())
}

func (ac *addrConn) incrCallsSucceeded() {
	ac.channelz.ChannelMetrics.CallsSucceeded.Add(1)
}

func (ac *addrConn) incrCallsFailed() {
	ac.channelz.ChannelMetrics.CallsFailed.Add(1)
}

// ErrClientConnTimeout indicates that the ClientConn cannot establish the
// underlying connections within the specified timeout.
//
// Deprecated: This error is never returned by grpc and should not be
// referenced by users.
var ErrClientConnTimeout = errors.New("grpc: timed out when dialing")

// getResolver finds the scheme in the cc's resolvers or the global registry.
// scheme should always be lowercase (typically by virtue of url.Parse()
// performing proper RFC3986 behavior).
func (cc *ClientConn) getResolver(scheme string) resolver.Builder {
	for _, rb := range cc.dopts.resolvers {
		if scheme == rb.Scheme() {
			return rb
		}
	}
	return resolver.Get(scheme)
}

func (cc *ClientConn) updateConnectionError(err error) {
	cc.lceMu.Lock()
	cc.lastConnectionError = err
	cc.lceMu.Unlock()
}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Stop comparing against ErrClientConnTimeout — it is never produced by the library.
  2. Detect dial timeout via the error returned from DialContext/Invoke: check status.Code(err)==codes.Unavailable and/or errors.Is(err, context.DeadlineExceeded).
  3. Use grpc.WithTimeout (deprecated) or, preferably, a context.WithTimeout passed to the call to bound dial/RPC time.
  4. Inspect connectionError() / the wrapped cause for the real TCP/TLS failure underneath the timeout.

Example fix

// before — deprecated, never matches
if err == grpc.ErrClientConnTimeout { ... }

// after — use status + context
st, ok := status.FromError(err)
if ok && st.Code() == codes.Unavailable && errors.Is(err, context.DeadlineExceeded) {
    // dial/RPC timed out
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Do NOT reference ErrClientConnTimeout; detect timeouts via status+context
func isDialTimeout(err error) bool {
    return status.Code(err) == codes.Unavailable && errors.Is(err, context.DeadlineExceeded)
}

Try / catch

if errors.Is(err, context.DeadlineExceeded) || status.Code(err) == codes.Unavailable {
    // dial/RPC timed out; ErrClientConnTimeout is never returned by grpc
}

Prevention

When it happens

Trigger: Historically meant to indicate the connection could not be established within the timeout. In current grpc-go nothing returns it; the only way a user sees it is by importing and comparing against it themselves. Real dial timeouts come through as the context error / a status.Unavailable wrapping the underlying connection error.

Common situations: Legacy code that does if err == grpc.ErrClientConnTimeout; copying old examples; static analysis flagging the symbol; grepping for timeout handling.

Understand the failure class

Related errors


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