High-performance gRPC connection pool with pluggable selection strategies.
This module provides a DynamicSupervisor-based pool with:
- Zero GenServer.call hot path — channels stored in ETS for O(1) access
- Pluggable strategies — round-robin (atomics), random, power-of-two-choices
:persistent_termfor config — zero-copy reads- ETS with read/write concurrency — optimized for concurrent access
- Registry for health tracking
- Telemetry with proper GenServer-based status loop
Architecture
Pool.Supervisor (one_for_one)
├── PoolState (GenServer — owns ETS, stores persistent_term)
├── Registry (health tracking)
├── DynamicSupervisor (manages workers)
├── WorkerStarter (temporary Task)
└── TelemetryReporter (GenServer — periodic status)Hot Path
get_channel/1 does zero GenServer calls:
ETS.lookup(:channel_count) → Strategy.select → ETS.lookup({:channel, idx}) → channel
Summary
Functions
Blocks until at least one channel is connected or timeout is reached.
Returns the child specification for supervision trees.
Gets all worker PIDs in the pool.
Gets a gRPC channel from the pool.
Resize pool to exact size.
Dynamically remove workers from the pool.
Dynamically add workers to the pool.
Starts a new connection pool.
Gets pool status and statistics.
Stops a connection pool.
Functions
@spec await_ready(atom(), pos_integer()) :: :ok | {:error, :timeout}
Blocks until at least one channel is connected or timeout is reached.
Useful for application startup to ensure the pool is ready before serving traffic.
Examples
{:ok, _} = GrpcConnectionPool.Pool.start_link(config)
:ok = GrpcConnectionPool.Pool.await_ready(MyPool, 5_000)
Returns the child specification for supervision trees.
Gets all worker PIDs in the pool.
@spec get_channel(atom()) :: {:ok, GRPC.Channel.t()} | {:error, :not_connected}
Gets a gRPC channel from the pool.
Zero GenServer calls — reads directly from ETS using the configured selection strategy (default: atomics-based round-robin).
Returns
{:ok, channel}— Successfully retrieved a connected channel{:error, :not_connected}— No healthy connections available
Examples
case GrpcConnectionPool.Pool.get_channel() do
{:ok, channel} -> MyService.Stub.call(channel, request)
{:error, :not_connected} -> {:error, :unavailable}
end
@spec resize(atom(), pos_integer()) :: {:ok, non_neg_integer()} | {:error, term()}
Resize pool to exact size.
@spec scale_down(atom(), pos_integer()) :: {:ok, non_neg_integer()} | {:error, term()}
Dynamically remove workers from the pool.
Returns
{:ok, new_size}— Successfully removed workers{:error, reason}— Failed to remove workers
@spec scale_up(atom(), pos_integer()) :: {:ok, non_neg_integer()} | {:error, term()}
Dynamically add workers to the pool.
Returns
{:ok, new_size}— Successfully added workers{:error, reason}— Failed to add workers
@spec start_link( GrpcConnectionPool.Config.t() | keyword(), keyword() ) :: Supervisor.on_start()
Starts a new connection pool.
Options
:name— Pool name (overrides config name)
Examples
{:ok, config} = GrpcConnectionPool.Config.production(host: "api.example.com", pool_size: 8)
{:ok, pid} = GrpcConnectionPool.Pool.start_link(config)
Gets pool status and statistics.
@spec stop(atom()) :: :ok
Stops a connection pool.