Skip to main content

RateLimiter

Struct RateLimiter 

Source
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 ClockSystemClock 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>

Source

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);
Source

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));
Source

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);
Source

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>

Source

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());
Source

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);
Source

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);
Source

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));
Source

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 reached
Source

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 spent
Source

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);
Source

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);
Source

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));
Source

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);
Source

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);

Trait Implementations§

Source§

impl<C: Clock + Clone> Debug for RateLimiter<C>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the limiter without exposing any key. Keys can be caller identities or other sensitive values, so only the configuration and the live key count are shown.

Source§

impl<C: Clock + Clone> From<RateLimiter<C>> for AsyncLimiter<C>

Source§

fn from(inner: RateLimiter<C>) -> Self

Converts to this type from the input type.
Source§

impl<C: Clock + Clone> Limiter for RateLimiter<C>

Source§

fn check_n(&self, key: impl Into<Key>, n: u32) -> Decision

Checks n units against key, returning the Decision.
Source§

fn check(&self, key: impl Into<Key>) -> Decision

Checks a single unit against key. Equivalent to check_n(key, 1).

Auto Trait Implementations§

§

impl<C> Freeze for RateLimiter<C>
where C: Freeze,

§

impl<C> RefUnwindSafe for RateLimiter<C>
where C: RefUnwindSafe,

§

impl<C> Send for RateLimiter<C>

§

impl<C> Sync for RateLimiter<C>

§

impl<C> Unpin for RateLimiter<C>
where C: Unpin,

§

impl<C> UnsafeUnpin for RateLimiter<C>
where C: UnsafeUnpin,

§

impl<C> UnwindSafe for RateLimiter<C>
where C: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<E> WithErrorCode<E> for E

Source§

fn with_code(self, code: impl Into<String>) -> CodedError<E>

Attach an error code to an error
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,