The core logic for a token-bucket rate limiter.
This just implements the logic for the limiting and has no method for ensuring consistency or blocking. Those can either be added by the application or a wrapping library.
Here is an example of applying an in-process rate limit. This should be wrapped up into a library and likely integrated with your favourite async runtime. However no one has done this yet.
```rust // Send at most 1 message per second per domain with a burst of 4. const DOMAINLIMIT: rlcore::Config = rlcore::Config::new( std::time::Duration::fromsecs(1), 4);
lazystatic::lazystatic! {
// This uses a Mutex
fn send_mail(msg: Message) { // Calculate our rate-limit key. let domain = msg.to().email().domain();
// Get (or create) the rate-limit tracker for this key.
let tracker_arc = DOMAIN_TO_RATE_LIMIT.lock().unwrap()
.entry(domain)
.or_default()
.clone();
// Lock the tracker itself.
let tracker = tracker_arc.lock().unwrap();
// Acquire the required tokens.
loop {
match rate_limit.acquire(&DOMAIN_LIMIT, 1) {
Ok(()) => break, // Granted.
Err(rl_core::Denied::TooEarly(denial)) => {
// Wait for required token grants.
if let Ok(to_wait) = denial.available_at().duration_since(std::time::SystemTime::now()) {
std::time::sleep(to_wait)
}
// Restart the loop. The next acquire shuold always succeed unless time has jumped backwards.
}
Err(e) => panic!("Error: {}", e),
}
}
std::mem::drop(tracker); // Unlock rate limit tracker.
// Actualy send the message...
unimplemented!("Send {:?}", msg)
} ```
Here is a simple example of applying a per-user rate limit to login attempts. In this example it is assumed that we can acquire row-level locks from our DB to ensure serialized rate-limit updates.
```rust // Rate login attempts to to 1 per hour with a 10 login burst. const LOGINRATELIMIT: rlcore::Config = rlcore::Config::new( std::time::Duration::from_secs(3600), 10);
fn trylogin(user: String, pass: String) { let User{passwordhash, mut ratelimit} = fetchandlockuser(&user);
if let Err(e) = rate_limit.acquire(&LOGIN_RATE_LIMIT, 1) {
panic!("Login failed: {}", e)
}
store_rate_limit_and_unlock_user(&user, &rate_limit);
// Verify password and issue token ...
} ```
If your DB doesn't support row-level updates you can do optimistic checking where after acquiring the rate limit you compare-and-set the new value. If the compare fails someone else acquired it in the meantime and you need to retry. For high-throughput use-cases you likely want to manage rate limits on a shared-service and utilize rl-core on that service. (This service has not yet been written.)
use_serde
.)It is intended that the following could be implemented on top of rl-core.