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/2dispatches 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
- Measurements:
[: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
- Measurements:
[: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 inawait_ready/2.- Measurements:
:system_time - Metadata:
:name,:target,:pid,:caller
- Measurements:
[:grpc, :client, :connection, :await_ready, :stop]– the wait ended.- Measurements:
:duration– as measured throughSystem.monotonic_time() - Metadata:
:name,:target,:pid,:caller,:result–:okwhen the connection became ready,:disconnectedwhen it shut down while the caller was waiting,:abandonedwhen the caller died or timed out and re-entered the wait
- Measurements:
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
42Using 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.
Functions
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}
endIt 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)
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.
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 longconnect/2waits 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}– aGRPC.Channelusable 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, %{})
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{}}
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.
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.
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 chosenGRPC.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}}
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.
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.