# `Chronicle.Connections.LoadBalancer`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/connections/load_balancer.ex#L4)

Selects a Chronicle server address to connect to from a resolved candidate list.

`Chronicle.Connections.Connection` re-resolves its configured (or DNS
SRV-resolved, via `Chronicle.Connections.DnsResolver`) addresses on every
connect/reconnect attempt and asks `select/4` to pick one, according to the
connection string's `loadBalancer` strategy:

  * `:least_connections` (the default) — probes every candidate's
    `GET /connections/count` HTTP endpoint on the Chronicle kernel and picks
    the address reporting the fewest active connections, breaking ties
    randomly. Before every probe attempt (not just the first), a random
    jitter of up to `jitter_max_ms` (0-250ms, defaulting to 250ms; 0
    disables it) is slept, so a fleet of clients reconnecting at the same
    moment doesn't stampede every candidate host simultaneously - this
    mirrors the reference .NET client. The winner is then best-effort
    informed via `POST /connections/reserve`, so other clients racing to
    connect at the same time see an up-to-date count. If every probe fails
    (e.g. none of the candidates expose the endpoint), selection falls back
    to `:random` rather than failing the connection attempt outright. The
    actual HTTP calls live in `Chronicle.Connections.LoadBalancer.HttpProbe`.
  * `:round_robin` — cycles through the candidates in order. The starting
    point is a random offset chosen once per `Connection` process (not
    index `0`), so a fleet of clients doesn't all dial the first host first.
  * `:random` — picks uniformly at random on every attempt.

## Testing

The HTTP probe used by `:least_connections` is injectable via the
`:probe_fun` option on `Chronicle.Connections.Connection`, mirroring the
`:connect_fun`/`:disconnect_fun` seam already used to fake the gRPC channel.

# `probe_action`

```elixir
@type probe_action() :: :count | :reserve
```

# `probe_fun`

```elixir
@type probe_fun() :: (probe_action(),
                Chronicle.Connections.ConnectionString.ServerAddress.t(),
                Chronicle.Connections.ConnectionString.t() -&gt;
                  {:ok, term()} | {:error, term()})
```

# `default_probe`

```elixir
@spec default_probe(
  probe_action(),
  Chronicle.Connections.ConnectionString.ServerAddress.t(),
  Chronicle.Connections.ConnectionString.t()
) :: {:ok, term()} | {:error, term()}
```

Default `:probe_fun` implementation, delegating to
`Chronicle.Connections.LoadBalancer.HttpProbe`.

# `select`

```elixir
@spec select(
  [Chronicle.Connections.ConnectionString.ServerAddress.t()],
  Chronicle.Connections.ConnectionString.t(),
  integer(),
  probe_fun(),
  non_neg_integer()
) ::
  {:ok, Chronicle.Connections.ConnectionString.ServerAddress.t()}
  | {:error, :no_addresses}
```

Selects one address from `addresses` according to
`connection_string.load_balancer`.

`round_robin_counter` is an ever-incrementing integer owned by the calling
`Connection` process — this module holds no state of its own, since the
counter (and any future strategy state) belongs to the one `Connection`
GenServer it is scoped to.

`jitter_max_ms` bounds the random delay `:least_connections` sleeps before
every probe attempt; it defaults to 250ms and 0 disables it entirely.
Ignored by `:round_robin` and `:random`.

Returns `{:error, :no_addresses}` when `addresses` is empty.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
