Skip to main content

WindowQuotaTable

Struct WindowQuotaTable 

Source
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: Polymarket 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 count tokens and refilling count per period admits 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 Polymarket’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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn build(self) -> RateLimiter

The limiter, with every bucket full.

Source

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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more