Managing Connections

1. Understanding Connection Lifecycle

StateMeaning
IDLENo active transport (initial with NewClient)
CONNECTINGResolving + handshaking
READYHTTP/2 established
TRANSIENT_FAILURELast attempt failed; backing off
SHUTDOWNClosed permanently

2. Configuring Connection Timeout

SettingDetail
Server ConnectionTimeoutHandshake deadline
Client ctx deadlineBounds first RPC including connect
Backoff configReconnect schedule

3. Implementing Connection Pooling

Example: Round-robin pool

type Pool struct {
    conns []*grpc.ClientConn
    next  atomic.Uint64
}
func (p *Pool) Get() *grpc.ClientConn {
    i := p.next.Add(1) % uint64(len(p.conns))
    return p.conns[i]
}
UseDetail
High throughput>10k RPS where one conn saturates
Usually unnecessaryHTTP/2 multiplexing handles most loads

4. Reusing Connections

RuleDetail
SingletonOne *grpc.ClientConn per upstream
Share stubsStubs are thread-safe
App lifetimeClose only on shutdown

5. Handling Connection Failures

Failure TypeBehavior
DNS failureTRANSIENT_FAILURE; resolver retries
TCP refusedExponential backoff reconnect
TLS errorFail until cert/SNI fixed

6. Detecting Connection State

Example: Read state

state := conn.GetState()
fmt.Println("state:", state) // IDLE/CONNECTING/READY/...
conn.Connect() // hint to leave IDLE

7. Waiting for Connection Ready

Example: Wait for READY

for conn.GetState() != connectivity.Ready {
    if !conn.WaitForStateChange(ctx, conn.GetState()) {
        return ctx.Err()
    }
}

8. Closing Connections Gracefully

StepDetail
Stop acceptingTrigger app-level drain
Wait for in-flightBounded by timeout
conn.Close()Releases resources

9. Handling Connection Leaks

SymptomCause
FD exhaustionNew conn per RPC
Goroutine growthStreams not closed
Toolpprof goroutine + file descriptors

10. Using Connection State Callbacks

APIDetail
conn.WaitForStateChange(ctx, current)Blocks until state moves
Custom resolver/balancerReceive subchannel state via callbacks