pub struct RateLimiter { /* private fields */ }Expand description
In-process two-layer rate limiter.
Layer 1 (primary): (TokenId, TenantId) bucket — keeps a shared token
from being drained by a single tenant.
Layer 2 (backstop): TokenId bucket — caps aggregate usage by a single
token across all tenants. Inherited from the v0.4 design; kept active so
pre-multi-tenant operators see no behavioural regression.
Cheaply cloneable: every clone shares the same underlying DashMaps
and Clock via Arc.
Implementations§
Source§impl RateLimiter
impl RateLimiter
Sourcepub const MAX_TENANTS_PER_TOKEN: usize = 4096
pub const MAX_TENANTS_PER_TOKEN: usize = 4096
Maximum number of distinct tenants tracked per token in the
per-(token, tenant) bucket map before LRU eviction kicks in (L10).
Sized generously relative to any realistic legitimate fan-out: a
single token addressing more than a few thousand tenants is either a
misconfiguration or an attack, and in both cases bounding the memory
is the right call. The eviction only ever discards rate-limiter
bookkeeping (a f64 balance + two Instants); a subsequently-evicted
tenant simply starts from a full burst on its next request, which is
strictly more permissive — never a correctness or security regression.
Sourcepub fn new(cfg: RateLimitConfig) -> Self
pub fn new(cfg: RateLimitConfig) -> Self
Construct a limiter with the production RealClock.
Sourcepub fn with_clock(cfg: RateLimitConfig, clock: Arc<dyn Clock>) -> Self
pub fn with_clock(cfg: RateLimitConfig, clock: Arc<dyn Clock>) -> Self
Construct a limiter with an injected clock (for tests).
Sourcepub fn is_disabled(&self) -> bool
pub fn is_disabled(&self) -> bool
true if every configured layer admits unconditionally.
Sourcepub fn config(&self) -> RateLimitConfig
pub fn config(&self) -> RateLimitConfig
Effective configuration this limiter was built with.
Sourcepub fn try_admit(&self, token: TokenId, tenant: TenantId) -> AdmitResult
pub fn try_admit(&self, token: TokenId, tenant: TenantId) -> AdmitResult
Attempt to claim one permit for the (token, tenant) pair.
Both the per-tenant (primary) and per-token (backstop) buckets must
admit. If either rejects we return AdmitResult::Reject carrying
the smaller of the two retry Durations — per the
per-tenant-bucket design note, the smaller backoff is the earliest
the client could plausibly retry, even though it may still face the
other bucket on the next attempt.
To avoid leaking a permit on one layer when the other rejects, we
hold both layers’ inner mutexes across the decision and only deduct
when both would admit. The lock-order is (token, tenant) then
token; since these live in two distinct DashMaps and every
caller takes them in the same order, no cycle is possible.
Trait Implementations§
Source§impl Clone for RateLimiter
impl Clone for RateLimiter
Source§fn clone(&self) -> RateLimiter
fn clone(&self) -> RateLimiter
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl !RefUnwindSafe for RateLimiter
impl !UnwindSafe for RateLimiter
impl Freeze for RateLimiter
impl Send for RateLimiter
impl Sync for RateLimiter
impl Unpin for RateLimiter
impl UnsafeUnpin for RateLimiter
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> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
Source§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
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