Skip to main content

stygian_proxy/strategy/
least_used.rs

1//! Least-used proxy rotation strategy.
2
3use std::sync::atomic::Ordering;
4
5use async_trait::async_trait;
6
7use crate::error::{ProxyError, ProxyResult};
8use crate::strategy::{ProxyCandidate, RotationStrategy, healthy_candidates};
9
10/// Selects the healthy proxy with the fewest total requests.
11///
12/// Ties are broken by position (the first minimum in the slice wins),
13/// giving stable and predictable behaviour.
14///
15/// Runs in O(n) over the healthy candidate slice; suitable for pools of up to
16/// ~10,000 proxies.
17///
18/// # Example
19/// ```
20/// # tokio_test::block_on(async {
21/// use stygian_proxy::strategy::{LeastUsedStrategy, RotationStrategy, ProxyCandidate};
22/// use stygian_proxy::types::ProxyMetrics;
23/// use std::sync::{Arc, atomic::Ordering};
24/// use uuid::Uuid;
25///
26/// let strategy = LeastUsedStrategy;
27/// let busy = Arc::new(ProxyMetrics::default());
28/// busy.requests_total.store(100, Ordering::Relaxed);
29/// let idle = Arc::new(ProxyMetrics::default());
30/// let candidates = vec![
31///     ProxyCandidate { id: Uuid::from_u128(1), weight: 1, metrics: busy,   healthy: true },
32///     ProxyCandidate { id: Uuid::from_u128(2), weight: 1, metrics: idle,   healthy: true },
33/// ];
34/// let chosen = strategy.select(&candidates).await.unwrap();
35/// assert_eq!(chosen.id, Uuid::from_u128(2), "should pick the idle proxy");
36/// # })
37/// ```
38#[derive(Debug, Default, Clone, Copy)]
39pub struct LeastUsedStrategy;
40
41#[async_trait]
42impl RotationStrategy for LeastUsedStrategy {
43    async fn select<'a>(
44        &self,
45        candidates: &'a [ProxyCandidate],
46    ) -> ProxyResult<&'a ProxyCandidate> {
47        let healthy = healthy_candidates(candidates);
48        healthy
49            .into_iter()
50            .min_by_key(|c| c.metrics.requests_total.load(Ordering::Relaxed))
51            .ok_or(ProxyError::AllProxiesUnhealthy)
52    }
53}