// The gRPC client connection pool + picker (← gRPC's subchannel pool and LB picker): the Channel
// holds one `SubConn` per resolved address and, for each call, picks a READY one according to the
// load-balancing policy. This composes the connectivity state machine (which sub-connections are
// usable) with the LB policy (which usable one to use). It is the pure decision core — the Channel
// dials the sub-connections over real sockets and calls their transition methods as events happen.
///|
/// A pool of sub-connections, one per resolved address (in resolver order), that picks a READY one
/// per an `LbPolicy`.
pub struct ConnectionPool {
order : Array[String]
subconns : Map[String, SubConn]
policy : LbPolicy
mut cursor : Int
}
///|
/// A pool over `addresses` (each a `host:port`), each sub-connection starting IDLE, choosing among
/// the READY ones with `policy` (default `PickFirst`).
pub fn ConnectionPool::new(
addresses : Array[String],
policy? : LbPolicy = PickFirst,
) -> ConnectionPool {
let order : Array[String] = []
let subconns : Map[String, SubConn] = Map([])
for address in addresses {
order.push(address)
subconns[address] = SubConn::new(address)
}
{ order, subconns, policy, cursor: 0 }
}
///|
/// The sub-connection for `address`, if the pool has one — the handle the Channel drives.
pub fn ConnectionPool::subconn(
self : ConnectionPool,
address : String,
) -> SubConn? {
self.subconns.get(address)
}
///|
/// The addresses of every READY sub-connection, in resolver order.
pub fn ConnectionPool::ready_addresses(self : ConnectionPool) -> Array[String] {
let out : Array[String] = []
for address in self.order {
if self.subconns.get(address) is Some(sc) && sc.is_ready() {
out.push(address)
}
}
out
}
///|
/// Pick a READY sub-connection's address for the next call, applying the LB policy over just the
/// ready ones (← gRPC picker). `None` when no sub-connection is ready.
pub fn ConnectionPool::pick(self : ConnectionPool) -> String? {
let ready = self.ready_addresses()
let n = ready.length()
guard n > 0 else { return None }
match self.policy {
PickFirst => Some(ready[0])
RoundRobin => {
let address = ready[self.cursor % n]
self.cursor = (self.cursor + 1) % n
Some(address)
}
}
}