03 / RESILIENCE
ratelimit
A token-bucket limiter over moonward's bucket, on the millisecond clock, and the rate_limit middleware, which answers 429 when it is empty.
WHERE IT SITS
INTERFACE
struct TokenBucketstructA token-bucket rate limiter kept in this process: the bucket holds up to burst tokens and refills continuously at rate a second, and each admitted request spends one. Every decision is a function of the state and now, so the limiter is testable without a real clock. The bucket is local, so N replicas admit N times the rate. RedisTokenLimit is the same limiter with its bucket in redis and is what a fleet should run; this one serves a single process, and is what RedisTokenLimit itself falls back to when redis is unreachable.
fn TokenBucket::new( rate : Double, burst~ : Double = -1.0, now~ : Int64 = 0) -> TokenBucketfnBuild a bucket admitting rate requests per second on average with room for a burst of that many back-to-back (default burst = rate). It starts full at time now, in milliseconds. The bucket is moonward's; this keeps shape and its millisecond clock. A capacity below one token is raised to one, because a bucket that can never hold a whole token admits nothing at all — which is what a fractional rate with no burst used to give.
fn TokenBucket::allow(self : TokenBucket, now : Int64) -> BoolfnTry to admit one request at time now. Returns true when admitted, false when the bucket is empty.
fn TokenBucket::allow_n( self : TokenBucket, n : Double, now : Int64) -> BoolfnTry to admit a request costing n tokens at time now. Returns false (spending nothing) when fewer than n tokens are available.
fn TokenBucket::available(self : TokenBucket, now : Int64) -> DoublefnThe (fractional) number of tokens currently available at now. Useful for metrics and tests.
let token_script : String =letcore/limit/tokenscript.lua, verbatim: read the bucket and the second it was last touched, refill by the elapsed seconds capped at capacity, spend requested if that many are there, and write both back under a TTL of two fill times. Public so a caller can pre-load it, and so a redis double can evaluate the very text the limiter ships.
struct RedisTokenLimitstructA token-bucket rate limiter whose bucket lives in redis. The token count and the second it was last refilled sit under {key}.tokens and {key}.ts, so every replica pointed at the same redis and key spends from one bucket. When redis cannot be reached the limiter keeps limiting from rescue, its process-local TokenBucket, the way drops to its in-process rescueLimiter rather than letting an outage open the gate.
fn RedisTokenLimit::new( client : @moonredis.Client, rate~ : Int32, burst~ : Int32, key~ : String) -> RedisTokenLimitfnA limiter admitting rate requests per second with room for a burst of that many back-to-back, over the bucket key names in client's redis.
fn RedisTokenLimit::rescue(self : RedisTokenLimit) -> TokenBucketfnThe process-local bucket this limiter falls back to, so a caller can inspect it.
async fn RedisTokenLimit::allow( self : RedisTokenLimit, now_ms : Int64) -> BoolitemTry to admit one request at now_ms.
async fn RedisTokenLimit::allow_n( self : RedisTokenLimit, n : Int32, now_ms : Int64) -> BoolitemTry to admit a request costing n tokens at now_ms. The script is handed whole seconds because hands it now.Unix(): the shared bucket refills at one-second granularity however finely the clock is read. Lua's false arrives as a null reply and its true as 1; anything else, and any redis failure, is served by the local bucket instead.
fn RedisTokenLimit::is_alive(self : RedisTokenLimit) -> BoolfnWhether this limiter is drawing on the shared bucket. false means it is on its local one, waiting for the probe to find redis again.
fn RedisTokenLimit::mark_down(self : RedisTokenLimit) -> UnitfnMark redis as unreachable without waiting for a call to fail — what a liveness probe does when it sees the connection drop.
fn RedisTokenLimit::mark_alive(self : RedisTokenLimit) -> UnitfnMark redis as answering again. The shared bucket still holds whatever the last replica left in it, so the limiter picks up the fleet's quota rather than a fresh one.
fn RedisTokenLimit::probing(self : RedisTokenLimit) -> BoolfnWhether a background probe is running.
async fn RedisTokenLimit::probe(self : RedisTokenLimit) -> BoolitemPing redis once and switch the limiter back to the shared bucket when it answers. The reading is the caller's: an unreachable redis is not an error here, it is the reason the local bucket exists.
async fn RedisTokenLimit::probe_forever( self : RedisTokenLimit, interval_ms~ : Int32 = 100, report~ : (Bool) -> Unit = _alive => ()) -> UnititemProbe every interval_ms until cancelled, and hand each reading to report. This is the background half of the liveness model: the limiter marks itself down the moment a call fails, and this loop is what brings it back. It is a function rather than a task the limiter starts for itself, so where the task belongs — which group, which lifetime — stays the caller's decision.