pub struct PerKey<K, C = SystemClock>where
C: Clock,{ /* private fields */ }std only.Expand description
A throttle that keeps independent state per key.
Each distinct key — a tenant, a user, an API token — gets its own token bucket with the same configured rate, so one noisy key cannot spend another’s budget. State lives in a sharded concurrent map: keys are spread across shards by hash, each shard has its own lock, and an existing key’s acquire takes only a shard read lock plus the bucket’s own atomic accounting, so unrelated keys never contend and throughput scales with cores.
Memory is bounded by eviction (see Eviction): idle keys expire and a
hard cap bounds the total, so a flood of unique keys reaches a ceiling instead
of growing without limit. Eviction is lazy and per-shard — it runs while
inserting a new key, never on a background thread or the steady-state path.
The default policy is bounded (Eviction::default).
Like Throttle, the headline acquire waits; the
try_* variants do not.
§Examples
use throttle_net::PerKey;
// 100 requests per second, per tenant.
let limiter: PerKey<String> = PerKey::per_second(100);
limiter.acquire(&"tenant:42".to_string()).await?;Implementations§
Source§impl<K> PerKey<K, SystemClock>
impl<K> PerKey<K, SystemClock>
Sourcepub fn per_second(rate: u32) -> Self
pub fn per_second(rate: u32) -> Self
Sourcepub fn per_duration(amount: u32, period: Duration) -> Self
pub fn per_duration(amount: u32, period: Duration) -> Self
Creates a per-key limiter giving every key amount units every period.
§Examples
use std::time::Duration;
use throttle_net::PerKey;
// 1000 units per minute, per key.
let limiter: PerKey<String> = PerKey::per_duration(1000, Duration::from_secs(60));Source§impl<K, C> PerKey<K, C>
impl<K, C> PerKey<K, C>
Sourcepub fn with_clock<C2>(self, clock: C2) -> PerKey<K, C2>
pub fn with_clock<C2>(self, clock: C2) -> PerKey<K, C2>
Replaces the time source, for deterministic tests with a
ManualClock. The store is rebuilt empty around
the new clock.
§Examples
use std::sync::Arc;
use std::time::Duration;
use clock_lib::ManualClock;
use throttle_net::PerKey;
let clock = Arc::new(ManualClock::new());
let limiter = PerKey::<&str>::per_second(1).with_clock(clock.clone());
assert!(limiter.try_acquire(&"k"));
assert!(!limiter.try_acquire(&"k"));
clock.advance(Duration::from_secs(1));
assert!(limiter.try_acquire(&"k"));Sourcepub fn with_eviction(self, eviction: Eviction) -> Self
pub fn with_eviction(self, eviction: Eviction) -> Self
Sets the memory-bound policy (idle TTL and/or hard key cap).
§Examples
use std::time::Duration;
use throttle_net::{Eviction, PerKey};
let limiter: PerKey<String> = PerKey::per_second(100)
.with_eviction(Eviction::capacity(50_000).with_idle(Duration::from_secs(300)));Sourcepub fn with_shards(self, shards: usize) -> Self
pub fn with_shards(self, shards: usize) -> Self
Sets the shard count (rounded up to a power of two, at least one).
More shards reduce contention between unrelated keys at the cost of a little memory. The store is rebuilt empty.
§Examples
use throttle_net::PerKey;
let limiter: PerKey<u64> = PerKey::per_second(100).with_shards(64);
assert_eq!(limiter.shard_count(), 64);Sourcepub fn shard_count(&self) -> usize
pub fn shard_count(&self) -> usize
The number of shards (a power of two).
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
The number of keys with live state across all shards.
A momentary, advisory snapshot — useful for tests and metrics, not a synchronization point.
Sourcepub fn try_acquire(&self, key: &K) -> bool
pub fn try_acquire(&self, key: &K) -> bool
Attempts to take one token for key without waiting.
§Examples
use throttle_net::PerKey;
let limiter: PerKey<&str> = PerKey::per_second(1);
assert!(limiter.try_acquire(&"a"));
assert!(!limiter.try_acquire(&"a"));
assert!(limiter.try_acquire(&"b")); // a different key is independentSourcepub fn try_acquire_with_cost(&self, key: &K, cost: u32) -> bool
pub fn try_acquire_with_cost(&self, key: &K, cost: u32) -> bool
Attempts to take cost tokens for key without waiting.
Source§impl<K, C> PerKey<K, C>
impl<K, C> PerKey<K, C>
Sourcepub async fn acquire(&self, key: &K) -> Result<(), ThrottleError>
Available on crate feature runtime only.
pub async fn acquire(&self, key: &K) -> Result<(), ThrottleError>
runtime only.Takes one token for key, waiting until one is available.
§Errors
Returns ThrottleError::CostExceedsCapacity when the per-key capacity
is zero.
§Examples
use throttle_net::PerKey;
let limiter: PerKey<String> = PerKey::per_second(100);
limiter.acquire(&"tenant:7".to_string()).await?;Sourcepub async fn acquire_with_cost(
&self,
key: &K,
cost: u32,
) -> Result<(), ThrottleError>
Available on crate feature runtime only.
pub async fn acquire_with_cost( &self, key: &K, cost: u32, ) -> Result<(), ThrottleError>
runtime only.Takes cost tokens for key, waiting until they are available.
§Errors
Returns ThrottleError::CostExceedsCapacity when cost exceeds the
per-key capacity, so the request can never be granted.