pub struct WindowQuotaTable { /* private fields */ }Expand description
Builds a RateLimiter from a venue’s window quotas: count requests in
any window of length period.
Every request passes the general bucket, then the buckets of the first row
whose pattern and method it matches, in the order the row lists them. Two
rows naming one BucketId share one allowance: one venue caps four
ledger routes at 900 per 10 s combined, which four buckets of their own
would turn into 3,600.
§How a bucket paces
Each bucket holds one token and refills one every
paced_interval, which aims at nine tenths of
count. There is no burst setting, on purpose:
- A token bucket admits its depth plus everything its refill adds. A
bucket holding
counttokens and refillingcountperperiodadmits twice the quota in one window. Depth is borrowed against the rate, so the least depth is also the most throughput. - A request that arrives during a hold waits before any bucket, and is paced when the hold ends. A request already waiting on a bucket when the hold begins takes its token, then waits out the hold too, and is released with the others when it ends: that burst is at most the requests in flight when the hold began (on the send loop, the concurrency permits).
- A published count is reachable as a burst and not as a rate. On
one venue’s
/closed-positions(150 per 10 s) a sustained 95% was refused and 90% ran clean for 180 s, so a bucket keeps a tenth back.
A count below 2 paces as 2 per window, since a bucket holds at least one token.
§Example
use std::time::Duration;
use polyoxide_core::{Matching, WindowQuotaTable};
use reqwest::Method;
let ten_seconds = Duration::from_secs(10);
let mut table = WindowQuotaTable::new(1_000, ten_seconds);
let ledger = table.bucket(900, ten_seconds);
table
.prefix("/positions", None, 150, ten_seconds)
.row(Matching::Prefix, "/trades", Some(Method::GET), &[ledger])
.row(Matching::Exact, "/orders", Some(Method::GET), &[ledger]);
let limiter = table.build();
// The general bucket first, then the row's.
let trades = limiter.effective_quota(&Method::GET, "/trades");
let orders = limiter.effective_quota(&Method::GET, "/orders");
assert_eq!(trades[1].bucket, orders[1].bucket);
assert_eq!(trades[1].admitted_in_one_window(), 810);
// An exact row does not match a sub-path.
assert_eq!(limiter.effective_quota(&Method::GET, "/orders/1").len(), 1);Implementations§
Source§impl WindowQuotaTable
impl WindowQuotaTable
Sourcepub fn new(count: u32, period: Duration) -> Self
pub fn new(count: u32, period: Duration) -> Self
A table whose general bucket admits count requests per period.
§Panics
When period is too short to pace count, since a bucket that paces
at an interval of zero would never hold anyone back: a zero period,
or one shorter than a nanosecond per request it paces.
Sourcepub fn bucket(&mut self, count: u32, period: Duration) -> BucketId
pub fn bucket(&mut self, count: u32, period: Duration) -> BucketId
Make a bucket admitting count requests per period, for rows to
name.
§Panics
When period is too short to pace count, since a bucket that paces
at an interval of zero would never hold anyone back: a zero period,
or one shorter than a nanosecond per request it paces.
Sourcepub fn row(
&mut self,
matching: Matching,
pattern: &'static str,
method: Option<Method>,
buckets: &[BucketId],
) -> &mut Self
pub fn row( &mut self, matching: Matching, pattern: &'static str, method: Option<Method>, buckets: &[BucketId], ) -> &mut Self
Add a row: a request whose path matches pattern, and whose method is
method when one is given, awaits buckets in order.
Only the first row a request matches applies, so a pattern goes before any shorter one that also matches it.
§Panics
When one of buckets was made by another table.
Sourcepub fn prefix(
&mut self,
pattern: &'static str,
method: Option<Method>,
count: u32,
period: Duration,
) -> &mut Self
pub fn prefix( &mut self, pattern: &'static str, method: Option<Method>, count: u32, period: Duration, ) -> &mut Self
Add a Matching::Prefix row with a bucket of its own, admitting
count requests per period.
§Panics
When period is too short to pace count, since a bucket that paces
at an interval of zero would never hold anyone back: a zero period,
or one shorter than a nanosecond per request it paces.
Sourcepub fn with_hold(&mut self, hold: Hold) -> &mut Self
pub fn with_hold(&mut self, hold: Hold) -> &mut Self
Wait out hold instead of a hold of the limiter’s own, so that
every layer sharing it holds together. Default: Hold::unbounded.
Sourcepub fn build(self) -> RateLimiter
pub fn build(self) -> RateLimiter
The limiter, with every bucket full.
Sourcepub fn paced_interval(count: u32, period: Duration) -> Duration
pub fn paced_interval(count: u32, period: Duration) -> Duration
The interval a bucket for count requests per period paces at:
period over nine tenths of count, less the one token of depth.
The one place the pacing formula lives; every bucket is built from it.