GRPC.Client.Connection (gRPC Client v1.0.3)

Copy Markdown View Source

Connection manager for gRPC client channels, with optional load balancing and name resolution support.

A Conn process manages one or more underlying gRPC connections (GRPC.Channel structs) and exposes a virtual channel to be used by client stubs. The orchestration process runs as a GenServer registered in a node-local Registry, so named channels are scoped to the current BEAM node.

Overview

  • connect/2 – establishes a client connection (single or multi-channel).
  • pick/2 – chooses a channel according to the active load-balancing policy.
  • disconnect/1 – gracefully closes a connection and frees resources.

Under the hood:

  • The target string is resolved using a Resolver.
  • Depending on the target and service config, a load-balancing module is chosen (e.g. PickFirst, RoundRobin).
  • Each call to pick/2 dispatches to the LB module, which selects a channel per request. DNS re-resolution reconciles the LB's channel list in place.

Supervised connections

For long-running clients, declare the connection in your own supervision tree instead of calling connect/2 at runtime. In that mode, establishment happens asynchronously after process start: if the backend is unreachable, the process stays alive and retries with exponential backoff instead of failing supervisor startup. Use await_ready/2 to block until the channel is usable.

children = [
  {GRPC.Client.Connection,
   name: MyApp.PaymentsConnection,
   target: "dns://payments.internal:50051",
   lb_policy: :round_robin}
]

:ok = GRPC.Client.Connection.await_ready(MyApp.PaymentsConnection, 10_000)
# use get_channel/1 for the non-raising variant
channel = GRPC.Client.Connection.get_channel!(MyApp.PaymentsConnection)
Payments.Stub.charge(channel, request)

The channel handle returned by get_channel/1 is valid as soon as the process is running, even while the connection is still being established; RPCs return {:error, :no_connection} style errors until then.

connect/2 keeps its historical fail-fast contract: it blocks until the first establishment attempt finishes and returns {:error, reason} (tearing the process down) if that attempt fails.

Supervisor memory

Connection start arguments (for example large default headers) can linger in the GRPC.Client.Supervisor process heap after children exit. That supervisor therefore hibernates when idle and runs fullsweep GCs more often. Tune via config:

config :grpc, GRPC.Client.Application,
  hibernate_after: 15_000,
  fullsweep_after: 20
  • :hibernate_after – idle ms before the supervisor hibernates (default: 15_000)
  • :fullsweep_after – minor GCs between fullsweeps on the supervisor (default: 20)

Target syntax

The target argument to connect/2 accepts URI-like strings that are resolved via the configured Resolver (default GRPC.Client.Resolver).

Examples of supported formats:

  • "dns://example.com:50051"
  • "ipv4:10.0.0.5:50051"
  • "unix:/tmp/my.sock"
  • "xds:///my-service"
  • "127.0.0.1:50051" (implicit DNS / fallback to IPv4)

See GRPC.Client.Resolver for the full specification.

Telemetry

Connection processes emit the following events:

  • [:grpc, :client, :connection, :connected] – establishment succeeded, including re-establishment after retries or a supervisor restart.
    • Measurements: :retry_attempt – failed attempts before this success
    • Metadata: :name, :target, :pid
  • [:grpc, :client, :connection, :connect_error] – an establishment attempt failed and a retry was scheduled.
    • Measurements: :retry_delay – milliseconds until the next attempt
    • Metadata: :name, :target, :pid, :reason, :retry_attempt
  • [:grpc, :client, :connection, :disconnected] – the connection process shut down, after its resources were released.
    • Measurements: none
    • Metadata: :name, :target, :pid, :reason
  • [:grpc, :client, :connection, :await_ready, :start] – a caller started waiting in await_ready/2.
    • Measurements: :system_time
    • Metadata: :name, :target, :pid, :caller
  • [:grpc, :client, :connection, :await_ready, :stop] – the wait ended.
    • Measurements: :duration – as measured through System.monotonic_time()
    • Metadata: :name, :target, :pid, :caller, :result:ok when the connection became ready, :disconnected when it shut down while the caller was waiting, :abandoned when the caller died or timed out and re-entered the wait

The :pid metadata is the connection process, useful for correlating events with process logs and crash reports.

Examples

Basic connect and RPC

iex> opts = [adapter: GRPC.Client.Adapters.Gun]
iex> {:ok, ch} = GRPC.Client.Connection.connect("127.0.0.1:50051", opts)
iex> req = %Grpc.Testing.SimpleRequest{response_size: 42}
iex> {:ok, resp} = Grpc.Testing.TestService.Stub.unary_call(ch, req)
iex> resp.response_size
42

Using interceptors and custom adapter

iex> opts = [interceptors: [GRPC.Client.Interceptors.Logger],
...>         adapter: GRPC.Client.Adapters.Mint]
iex> {:ok, ch} = GRPC.Client.Connection.connect("dns://my-service.local:50051", opts)
iex> {:ok, channel} = GRPC.Client.Connection.pick(ch)
iex> channel.host
"127.0.0.1"

Unix socket target

iex> {:ok, ch} = GRPC.Client.Connection.connect("unix:/tmp/service.sock")
iex> Grpc.Testing.TestService.Stub.empty_call(ch, %{})

Disconnect

iex> {:ok, ch} = GRPC.Client.Connection.connect("127.0.0.1:50051")
iex> GRPC.Client.Connection.disconnect(ch)
{:ok, %GRPC.Channel{...}}

Summary

Functions

Blocks until the connection has at least one established channel.

Returns a child spec for running a connection under a supervisor.

Establishes a new client connection to a gRPC server or set of servers.

Disconnects a channel previously returned by connect/2.

Returns the GRPC.Channel handle for a running named connection.

Same as get_channel/1, but raises if the connection is not running.

Picks a channel from the orchestrator according to the active load-balancing policy.

Triggers an immediate DNS re-resolution, subject to rate limiting.

Starts a connection process linked to the caller.

Types

t()

@type t() :: %GRPC.Client.Connection{
  adapter: module(),
  connect_opts: keyword(),
  established?: boolean(),
  last_error: term() | nil,
  lb_mod: module() | nil,
  lb_state: term() | nil,
  real_channels: %{
    required(String.t()) => {:connected, struct()} | {:failed, any()}
  },
  resolver: module() | nil,
  resolver_state: term() | nil,
  resolver_target: String.t() | nil,
  retry_attempt: non_neg_integer(),
  virtual_channel: struct(),
  waiters: [{pid(), GenServer.from(), reference(), integer()}]
}

Functions

await_ready(ref_or_channel, timeout \\ 5000)

Blocks until the connection has at least one established channel.

Returns :ok once established, or {:error, :timeout} if the connection is still retrying when timeout elapses. Unlike connect/2, a failed establishment attempt does not resolve the wait: the process keeps retrying with backoff and this call returns as soon as an attempt succeeds.

Returns {:error, :not_started} if the connection is not running, or if it is disconnected or shut down while waiting.

Examples

Waiting for a supervised connection before issuing the first RPC, for example inside a release readiness check or a worker's init/1:

case GRPC.Client.Connection.await_ready(MyApp.PaymentsConnection, 10_000) do
  :ok ->
    channel = GRPC.Client.Connection.get_channel!(MyApp.PaymentsConnection)
    Payments.Stub.charge(channel, request)

  {:error, :timeout} ->
    # Still unreachable after 10s; the process keeps retrying in the
    # background, so a later await_ready/2 or RPC may succeed.
    {:error, :payments_unavailable}
end

It also accepts the channel handle returned by get_channel/1:

{:ok, channel} = GRPC.Client.Connection.get_channel(MyApp.PaymentsConnection)
:ok = GRPC.Client.Connection.await_ready(channel, 10_000)

child_spec(init_arg)

Returns a child spec for running a connection under a supervisor.

Accepts the same options as connect/2 plus the required :target and :name keys. The spec only captures the target and options, so a restarted process re-resolves and re-dials from scratch instead of reusing stale state.

connect(target, opts \\ [])

Establishes a new client connection to a gRPC server or set of servers.

The target string determines how the endpoints are resolved (see Resolver).

Options:

  • :adapter – transport adapter module (default: GRPC.Client.Adapters.Gun)
  • :adapter_opts – options passed to the adapter
  • :resolver – resolver module (default: GRPC.Client.Resolver)
  • :lb_policy – load-balancing policy (:pick_first, :round_robin)
  • :interceptors – list of client interceptors
  • :codec – request/response codec (default: GRPC.Codec.Proto)
  • :compressor / :accepted_compressors – message compression
  • :headers – default metadata headers
  • :connect_timeout – how long connect/2 waits for the first establishment attempt in ms (default: 15000)
  • :resolve_interval – DNS re-resolution interval in ms (default: 30000)
  • :max_resolve_interval – backoff cap in ms (default: 300000)
  • :min_resolve_interval – rate-limit floor in ms (default: 5000)

Returns:

  • {:ok, channel} – a GRPC.Channel usable with stubs
  • {:error, {:already_started_with_different_target, target}} – the name is already registered to a connection with a different target
  • {:error, reason} – if connection fails

Examples

iex> {:ok, ch} = GRPC.Client.Connection.connect("127.0.0.1:50051")
iex> Grpc.Testing.TestService.Stub.empty_call(ch, %{})

disconnect(channel)

Disconnects a channel previously returned by connect/2.

This will close all underlying real connections for the orchestrator and stop its process.

Returns {:ok, channel} on success.

Example

iex> {:ok, ch} = GRPC.Client.Connection.connect("127.0.0.1:50051")
iex> GRPC.Client.Connection.disconnect(ch)
{:ok, %GRPC.Channel{}}

get_channel(name)

Returns the GRPC.Channel handle for a running named connection.

The handle is a lightweight identity struct: stubs resolve it to a healthy underlying connection on every RPC, so it stays valid across reconnects and endpoint changes and can be fetched once and cached. It is readable as soon as the process is running, even while establishment is still in progress.

Returns {:error, :not_started} if no connection with that name is running.

get_channel!(name)

Same as get_channel/1, but raises if the connection is not running.

pick_channel(channel, opts \\ [])

Picks a channel from the orchestrator according to the active load-balancing policy.

Normally, you don’t need to call pick/2 directly – client stubs do this automatically – but it can be useful when debugging or testing.

Returns:

  • {:ok, channel} – the chosen GRPC.Channel
  • {:error, :no_connection} – if the orchestrator is not available

Example

iex> {:ok, ch} = GRPC.Client.Connection.connect("dns://my-service.local:50051")
iex> GRPC.Client.Connection.pick(ch)
{:ok, %GRPC.Channel{host: "192.168.1.1", port: 50051}}

resolve_now(channel)

Triggers an immediate DNS re-resolution, subject to rate limiting.

Intended for use by health checks or heartbeat mechanisms that detect a backend has gone away and want to force a fresh DNS lookup.

start_link(opts)

Starts a connection process linked to the caller.

Accepts the same options as child_spec/1. Establishment (resolution and dialing) happens asynchronously after the process starts; use await_ready/2 to block until the channel is usable.

start_link(target, opts)