pub struct RateLimiter<C: Clock + Clone = SystemClock> { /* private fields */ }Expand description
A keyed rate limiter.
Tracks an independent allowance for every key it sees and answers
check in the time it takes to hash the key and run its
bucket. Per-key state lives in a sharded concurrent
store: unrelated keys land in different shards and never contend, an
existing-key check takes only a shared read lock plus the bucket’s atomic
accounting, and memory is bounded by eviction so a
flood of unique keys hits a cap instead of growing without limit. The
limiter is Send + Sync and is meant to be shared — behind an
Arc, or as a static — across all the threads serving
requests.
The default algorithm is the token bucket, whose accounting is delegated to
better-bucket: each key bursts up
to its Quota immediately, then sustains the quota rate as the allowance
refills. Time comes from an injectable Clock — SystemClock in
production, or a ManualClock in tests via with_clock.
§Examples
use rate_net::{RateLimiter, Decision};
// 100 requests per second, per key.
let limiter = RateLimiter::per_second(100);
match limiter.check("user:42") {
Decision::Allow => { /* serve the request */ }
Decision::Deny { retry_after } => {
// 429, Retry-After: retry_after
let _ = retry_after;
}
_ => {}
}Implementations§
Source§impl RateLimiter<SystemClock>
impl RateLimiter<SystemClock>
Sourcepub fn per_second(limit: u32) -> Self
pub fn per_second(limit: u32) -> Self
Creates a limiter allowing limit requests per second, per key, driven
by the OS monotonic clock.
The headline Tier-1 constructor. A limit of 0 yields a limiter that
denies every request.
§Examples
use rate_net::RateLimiter;
let limiter = RateLimiter::per_second(10);
assert_eq!(limiter.quota().limit(), 10);Sourcepub fn per_minute(limit: u32) -> Self
pub fn per_minute(limit: u32) -> Self
Creates a limiter allowing limit requests per minute, per key.
§Examples
use rate_net::RateLimiter;
use std::time::Duration;
let limiter = RateLimiter::per_minute(600);
assert_eq!(limiter.quota().period(), Duration::from_secs(60));Sourcepub fn with_quota(quota: Quota) -> Self
pub fn with_quota(quota: Quota) -> Self
Creates a limiter from an explicit Quota, driven by the OS monotonic
clock, with default sharding and a bounded-memory Eviction policy.
Use this with Quota::rate when the window is neither a second nor a
minute.
§Examples
use rate_net::{RateLimiter, Quota};
use std::time::Duration;
let quota = Quota::rate(5, Duration::from_millis(100))?;
let limiter = RateLimiter::with_quota(quota);
assert_eq!(limiter.quota().limit(), 5);Sourcepub fn builder() -> Builder<SystemClock>
pub fn builder() -> Builder<SystemClock>
Starts a Builder — the Tier-2 path that selects the
algorithm, quota, burst, shard count, eviction policy, and clock in one
fluent surface.
§Examples
use rate_net::{RateLimiter, Eviction};
use std::time::Duration;
let limiter = RateLimiter::builder()
.quota(1000, Duration::from_secs(60)) // 1000 / minute
.burst(50)
.shards(64)
.eviction(Eviction::idle(Duration::from_secs(300)))
.build();
assert_eq!(limiter.quota().limit(), 1000);
assert_eq!(limiter.quota().burst(), 50);Source§impl<C: Clock + Clone> RateLimiter<C>
impl<C: Clock + Clone> RateLimiter<C>
Sourcepub fn with_clock<C2: Clock + Clone>(self, clock: C2) -> RateLimiter<C2>
pub fn with_clock<C2: Clock + Clone>(self, clock: C2) -> RateLimiter<C2>
Replaces the limiter’s time source, discarding any per-key state.
This is the clock-injection seam, intended for use immediately after
construction. Injecting a ManualClock makes refill behaviour
deterministic so window and rollover tests run with no sleep. The shard
count and eviction policy are preserved.
§Examples
use rate_net::RateLimiter;
use clock_lib::ManualClock;
use std::sync::Arc;
use std::time::Duration;
let clock = Arc::new(ManualClock::new());
let limiter = RateLimiter::per_second(5).with_clock(Arc::clone(&clock));
// Drain the key's allowance.
for _ in 0..5 {
assert!(limiter.check("k").is_allow());
}
assert!(limiter.check("k").is_deny());
// Advance one second — no real sleep — and the allowance is back.
clock.advance(Duration::from_secs(1));
assert!(limiter.check("k").is_allow());Sourcepub fn with_algorithm(self, algorithm: Algorithm) -> Self
pub fn with_algorithm(self, algorithm: Algorithm) -> Self
Selects the algorithm, discarding any per-key state.
Intended immediately after construction. The leaky bucket and the window
algorithms require the algorithms feature; without it the only
selectable variant is Algorithm::TokenBucket.
§Examples
use rate_net::{RateLimiter, Algorithm};
let limiter = RateLimiter::per_second(100).with_algorithm(Algorithm::SlidingWindowCounter);
assert_eq!(limiter.algorithm(), Algorithm::SlidingWindowCounter);Sourcepub fn with_shards(self, shards: usize) -> Self
pub fn with_shards(self, shards: usize) -> Self
Sets the shard count, discarding any per-key state.
Intended immediately after construction. More shards reduce contention between unrelated keys; the value is rounded up to a power of two. A good starting point is a small multiple of the core count.
§Examples
use rate_net::RateLimiter;
let limiter = RateLimiter::per_second(1000).with_shards(64);
assert_eq!(limiter.shards(), 64);Sourcepub fn with_eviction(self, eviction: Eviction) -> Self
pub fn with_eviction(self, eviction: Eviction) -> Self
Sets the eviction policy, discarding any per-key state.
Intended immediately after construction. The default policy bounds memory with a generous key-capacity cap; override it to tune the cap or add an idle TTL.
§Examples
use rate_net::{RateLimiter, Eviction};
use std::time::Duration;
let limiter = RateLimiter::per_second(1000)
.with_eviction(Eviction::capacity(100_000).with_idle(Duration::from_secs(300)));
assert_eq!(limiter.eviction().max_keys(), Some(100_000));Sourcepub fn check(&self, key: impl Into<Key>) -> Decision
pub fn check(&self, key: impl Into<Key>) -> Decision
Checks a single unit against key.
Returns Decision::Allow if the key is within its limit (the unit is
counted), or Decision::Deny with the wait until it would be admitted.
The key can be anything that converts into a Key — a string, an IP
address, a user id.
§Examples
use rate_net::{RateLimiter, Decision};
let limiter = RateLimiter::per_second(1);
assert_eq!(limiter.check("user:42"), Decision::Allow);
assert!(limiter.check("user:42").is_deny()); // limit reachedSourcepub fn check_n(&self, key: impl Into<Key>, n: u32) -> Decision
pub fn check_n(&self, key: impl Into<Key>, n: u32) -> Decision
Checks n units against key in one operation.
Useful when a single request costs more than one unit (a batch, a
weighted endpoint). Either all n units are admitted or none are.
Requesting 0 always succeeds; requesting more than the quota can never
succeed, and the denial’s retry_after is Duration::MAX.
§Examples
use rate_net::{RateLimiter, Decision};
let limiter = RateLimiter::per_second(10);
assert_eq!(limiter.check_n("tenant:acme", 4), Decision::Allow);
assert_eq!(limiter.check_n("tenant:acme", 6), Decision::Allow);
assert!(limiter.check_n("tenant:acme", 1).is_deny()); // 10 spentSourcepub fn quota(&self) -> Quota
pub fn quota(&self) -> Quota
The quota every key is limited to.
§Examples
use rate_net::RateLimiter;
assert_eq!(RateLimiter::per_second(50).quota().limit(), 50);Sourcepub const fn algorithm(&self) -> Algorithm
pub const fn algorithm(&self) -> Algorithm
The algorithm this limiter applies.
§Examples
use rate_net::{RateLimiter, Algorithm};
assert_eq!(RateLimiter::per_second(1).algorithm(), Algorithm::TokenBucket);Sourcepub const fn eviction(&self) -> Eviction
pub const fn eviction(&self) -> Eviction
The eviction policy bounding the per-key store.
§Examples
use rate_net::{RateLimiter, DEFAULT_MAX_KEYS};
assert_eq!(RateLimiter::per_second(1).eviction().max_keys(), Some(DEFAULT_MAX_KEYS));Sourcepub fn shards(&self) -> usize
pub fn shards(&self) -> usize
The number of shards the per-key store is split across (a power of two).
§Examples
use rate_net::RateLimiter;
assert_eq!(RateLimiter::per_second(1).with_shards(32).shards(), 32);Sourcepub fn tracked_keys(&self) -> usize
pub fn tracked_keys(&self) -> usize
The number of keys with live state right now.
A momentary snapshot, advisory under concurrent access — and bounded by the eviction policy.
§Examples
use rate_net::RateLimiter;
let limiter = RateLimiter::per_second(1);
assert_eq!(limiter.tracked_keys(), 0);
let _ = limiter.check("a");
assert_eq!(limiter.tracked_keys(), 1);