Expand description
Per-token QPS + burst rate limiting (token-bucket).
This module implements PATH-TO-V1 v0.4 exit-criterion Rate limiting per
token. It layers behind crate::middleware::bearer_auth: every request
that survives auth carries an AuthContext in its extensions, and the
rate_limit middleware uses the contained TokenId to credit a
per-token TokenBucket.
§Design
§Token-bucket variant
We use a refill-on-take bucket (sometimes called lazy refill) rather than a background-tick refill. On every request:
- Compute elapsed nanos since the bucket’s
last_refill. - Add
elapsed * qps / 1_000_000_000permits totokens, clamped atburst. - If
tokens >= 1.0deduct one and admit; otherwise reject with429 Too Many Requestsand the appropriateRetry-Aftervalue.
Tradeoff: refill-on-take has zero background CPU cost and zero
coordination overhead (no ticker task), at the price of cold buckets
sitting in the DashMap until the process restarts. Since the
allowlist of bearer tokens is bounded by configuration size
(TENSOR_WASM_API_TOKENS is a finite comma-separated list), the cardinality
is small and bounded — a future TTL eviction sweep is a non-goal for
v0.4.0. A background-tick refiller would have been the wrong choice: it
requires a per-bucket schedule, awakens for idle tokens, and either holds
a global lock on the wake task or fragments scheduling per shard. The
refill-on-take math is two adds and a clamp; the lock is held for
microseconds.
§Sharding
Buckets live in Arc<DashMap<TokenId, Mutex<BucketState>>>. DashMap
provides shard-level read/write locks; the inner std::sync::Mutex
serialises refill arithmetic for a single bucket. We use std::sync::Mutex
rather than parking_lot::Mutex to avoid pulling a new dependency into
tensor-wasm-api; the critical section is a handful of integer ops with
no await points, so OS-mutex contention behaviour is acceptable.
§Clock injection
Unit tests need deterministic refill behaviour without tokio::time::sleep
(slow + flaky). The Clock trait abstracts “now”. Production uses
RealClock; tests inject ManualClock and advance it explicitly.
§Wiring
crate::server::build_router layers rate_limit after bearer_auth
and before any route handler. If RateLimitConfig::is_disabled returns
true (qps == 0 or burst == 0) the layer is installed but short-circuits
to a pass-through — equivalent to no rate limiting.
Structs§
- Auth
Context - Per-request authentication context inserted into
axum::http::Extensionsbycrate::middleware::bearer_authafter a successful auth check. - Manual
Clock - Test clock: holds an explicit
Instantthat the test advances. - PerTenant
Rate Limit Config - Per-tenant (secondary) rate-limit configuration.
- Rate
Limit Config - Static configuration for the rate limiter.
- Rate
Limiter - In-process two-layer rate limiter.
- Real
Clock - Production clock: delegates to
Instant::now. - TokenId
- Stable identifier for a bearer token within a single process lifetime.
Enums§
- Admit
Result - Outcome of an attempt to claim a permit from the bucket.
Traits§
- Clock
- Abstract monotonic clock. Implemented by
RealClock(production) andManualClock(tests).
Functions§
- rate_
limit - Axum middleware that enforces the per-token rate limit.