pub struct KeyedRateLimiter { /* private fields */ }Expand description
Per-key sliding-window rate limiter backed by a Mutex<HashMap>.
Each unique key (IP address, user ID, etc.) gets its own independent counter.
The check-and-update sequence is atomic: no TOCTOU race can allow more requests
than max_requests in any single window, even under high concurrency.
The map is capped at DEFAULT_MAX_ENTRIES keys. When a new key arrives at
capacity the entry with the oldest window_start is evicted to make room,
bounding memory growth while still tracking new sources.
§Deployment note
This rate limiter is per-process. In a multi-replica deployment, each
replica enforces the limit independently — the effective limit across N
replicas is N × limit. For true distributed enforcement, configure a
Redis-backed rate limiter via the redis-rate-limiting Cargo feature (see
the fraiseql-observers queue feature for the integration pattern). Call
warn_if_single_node_rate_limiting during server startup to emit a
reminder when no distributed backend is detected.
§Constructors
KeyedRateLimiter::new— use the system wall clock (production).KeyedRateLimiter::with_clock— inject a custom clock (testing).KeyedRateLimiter::with_clock_and_max_entries— custom clock + cap (testing).
Implementations§
Source§impl KeyedRateLimiter
impl KeyedRateLimiter
Sourcepub fn new(config: AuthRateLimitConfig) -> Self
pub fn new(config: AuthRateLimitConfig) -> Self
Create a new keyed rate limiter using wall-clock time.
Sourcepub fn with_max_entries(config: AuthRateLimitConfig, max_entries: usize) -> Self
pub fn with_max_entries(config: AuthRateLimitConfig, max_entries: usize) -> Self
Create a rate limiter with a custom entry cap.
Use this when the deployment context calls for a tighter or looser bound
than DEFAULT_MAX_ENTRIES. Setting max_entries = 0 disables the cap
(unbounded — not recommended in production).
Sourcepub fn with_clock<F>(config: AuthRateLimitConfig, clock: F) -> Self
pub fn with_clock<F>(config: AuthRateLimitConfig, clock: F) -> Self
Create a rate limiter with an injectable clock (for testing).
The clock function is called on every check() to obtain the current Unix timestamp.
Pass || u64::MAX to simulate a broken system clock and verify fail-open behavior.
Sourcepub fn with_clock_and_max_entries<F>(
config: AuthRateLimitConfig,
max_entries: usize,
clock: F,
) -> Self
pub fn with_clock_and_max_entries<F>( config: AuthRateLimitConfig, max_entries: usize, clock: F, ) -> Self
Create a rate limiter with both a custom clock and a custom entry cap (for testing).
Combines the benefits of KeyedRateLimiter::with_clock and
KeyedRateLimiter::with_max_entries for deterministic eviction tests.
Sourcepub fn check(&self, key: &str) -> Result<()>
pub fn check(&self, key: &str) -> Result<()>
Check if a request should be allowed for the given key
§Atomicity
This operation is atomic - the entire check-and-update sequence happens atomically:
- Acquires exclusive lock on rate limit records
- Gets current timestamp
- Loads or creates request record for this key
- Decides: allow, reset window, or deny
- Updates counter/window only if request is allowed
- Releases lock
No concurrent thread can observe a partial state. This prevents classic time-of-check-time-of-use (TOCTOU) race conditions where multiple threads simultaneously exceed the rate limit.
§Returns
Ok(()) if the request is allowed and the counter has been incremented.
§Errors
Returns AuthError::RateLimited if the key has exceeded the configured
rate limit within the sliding window.
§Panics
Panics if the Mutex is poisoned (another thread panicked while holding the lock). This is acceptable because a poisoned lock indicates a thread panic, suggesting the system is already in an inconsistent state and should be restarted.
Sourcepub fn active_limiters(&self) -> usize
pub fn active_limiters(&self) -> usize
Get the number of active rate limiters (for monitoring).
Sourcepub fn clone_config(&self) -> AuthRateLimitConfig
pub fn clone_config(&self) -> AuthRateLimitConfig
Create a copy for independent testing
Auto Trait Implementations§
impl !Freeze for KeyedRateLimiter
impl !RefUnwindSafe for KeyedRateLimiter
impl Send for KeyedRateLimiter
impl Sync for KeyedRateLimiter
impl Unpin for KeyedRateLimiter
impl UnsafeUnpin for KeyedRateLimiter
impl !UnwindSafe for KeyedRateLimiter
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more