GrpcConnectionPool.PoolState (grpc_connection_pool v0.4.0)

Copy Markdown View Source

ETS table owner and serializer for slot assignment.

This GenServer owns the pool's ETS table. The table is :public so the hot path (Pool.get_channel/1) can read it lock-free, but every write to the channel-slot layout (claiming/releasing a slot, inserting/removing a channel, and the :channel_count increment/decrement) is funneled through this process via register_channel/3 and unregister_channel/2. Because a single GenServer processes those calls one at a time, concurrent worker connects/disconnects can never interleave a read-modify-write on the slot map — slots stay a contiguous 0..channel_count-1 range and the count always matches the number of populated {:channel, index} entries.

Note: the table is not crash-resilient. It has no :heir, so if this process dies the :named_table is destroyed and re-created empty on restart; workers re-populate it as they reconnect. (A real heir would require a separate long-lived owner process.)

The ETS table holds only data the hot path (or other processes) read:

  • {:channel, index}{channel, last_used_at} for O(1) indexed access
  • :channel_count — number of connected channels
  • :pool_size — expected pool size
  • :config — pool configuration (also stored in :persistent_term)
  • :scaling_lock — lock for scaling operations

The pid => slot_index map is not in ETS — get_channel/1 never reads it, so it lives in this GenServer's state. Keeping it out of ETS avoids copying the whole map in and out on every connect/disconnect, and since slots are kept contiguous, claiming a slot is O(1) (map_size).

Summary

Functions

Returns a specification to start this module under a supervisor.

Registers a worker's channel in a pool slot (serialized).

Releases a worker's slot and decrements :channel_count (serialized).

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

register_channel(pool_name, pid, channel)

@spec register_channel(atom(), pid(), term()) :: {:ok, non_neg_integer()}

Registers a worker's channel in a pool slot (serialized).

Assigns the worker the lowest free slot (or reuses its existing slot), inserts the channel into ETS, and bumps :channel_count. Returns {:ok, slot_index}. All mutations happen inside the GenServer so concurrent registrations cannot race.

unregister_channel(pool_name, pid)

@spec unregister_channel(atom(), pid()) :: :ok

Releases a worker's slot and decrements :channel_count (serialized).

Removes the worker's channel, compacts the slot array so the remaining channels stay contiguous, and decrements the count atomically with the slot mutation. No-op if the worker holds no slot.