Load Balancing
Every proxy component in rust-web-server — ReverseProxy, H2ReverseProxy, GrpcProxy, TcpProxy, UdpProxy, WsProxy, and DynamicProxy (config-driven mode) — uses the same underlying selection mechanism: a lock-free atomic round-robin counter.
How round-robin works
Each proxy struct holds an AtomicUsize counter initialised to 0. On every request the counter is incremented with Ordering::Relaxed and the backend index is computed as:
let idx = counter.fetch_add(1, Ordering::Relaxed) % backends.len();No mutex is acquired; the operation is a single CPU instruction on every major architecture. Under concurrent load requests are distributed evenly across all live backends in cyclic order.
Library API
use rust_web_server::proxy::{LoadBalancing, ReverseProxy};
// Explicit — same as the default.let proxy = ReverseProxy::new(["http://a:8080", "http://b:8080", "http://c:8080"]) .strategy(LoadBalancing::RoundRobin);The .strategy() method exists to allow future strategies to be added without a breaking API change.
Config-driven mode
In config-driven mode the strategy field in [[upstream]] is parsed and stored but only "round_robin" is active:
[[upstream]]name = "api"backends = ["api-1:3000", "api-2:3000", "api-3:3000"]strategy = "round_robin" # default; only supported value todayDynamicProxy and live backend lists
When [upstream.health_check] is configured, the health-checker thread maintains a shared Arc<RwLock<Vec<String>>> containing only the currently healthy backends. DynamicProxy (the config-driven proxy adapter) reads from this live list on every request:
All backends: [api-1, api-2, api-3] ↓ health checker removes api-2Live backends: [api-1, api-3] ↓ round-robin counter % 2Request N+0 → api-1Request N+1 → api-3Request N+2 → api-1The live list is protected by RwLock — reads are concurrent; the health-checker thread takes a write lock only when the list changes.
Failover behaviour
ReverseProxy tries backends in order starting from the round-robin cursor position. If a backend fails to connect, the next backend in the list is tried. This continues until either a backend succeeds or all backends have been tried (returning 502).
backends = [A, B, C]counter = 7 → idx 7 % 3 = 1 → start at BB fails → try C → C succeeds → return responseCanary / weighted round-robin
CanaryLayer implements weighted round-robin by pre-expanding the rotation: a backend with weight = 3 appears three times in the rotation array. The same lock-free atomic counter selects slots.
// 3 out of 4 slots → stable; 1 out of 4 → canary.CanaryLayer::new(vec![ WeightedBackend::new("http://stable:8080", 3), WeightedBackend::new("http://canary:8080", 1),])See Canary / Traffic Splitting for full details.