Skip to main content

TokenBucket

Struct TokenBucket 

Source
pub struct TokenBucket { /* private fields */ }

Implementations§

Source§

impl TokenBucket

Source

pub fn try_consume(&self, requested: u64) -> u64

Tries to consume up to requested whole tokens.

This method returns the whole tokens actually granted. A part token in the bucket is not granted; it stays for a later consume. A bucket in debt from Self::record grants nothing. Rate-0 grants the full request.

The refill, the grant, and the new balance are one critical section on the bucket’s group, so a concurrent Self::set_token_rate_with_burst runs wholly before or wholly after this call, and concurrent consumes never lose each other’s refill. See the module documentation of the runtime for the design.

§Panics

Panics if the injected clock reads more than u64::MAX nanoseconds since its origin, about 584 years.

Source

pub fn quota_exceeded(&self) -> bool

Whether the bucket has nothing left to grant: its balance, refilled up to now, is spent or in debt. A rate-0 bucket has no limit and is never exceeded.

This is Kafka’s ReplicationQuotaManager.isQuotaExceeded, which a replication throttle asks before it reads rather than after. While it answers false the read goes out whole, and the caller then charges its size with Self::record_bounded, past the balance if it must. The overshoot is debt, and the debt makes the next ask answer true until the refill has repaid it. So the long-term rate holds, and a batch larger than the whole burst still moves.

§Panics

Panics if the injected clock reads more than u64::MAX nanoseconds since its origin, about 584 years.

Source

pub fn record(&self, tokens: u64) -> u64

Charges tokens whole tokens whatever the bucket holds, and returns the debt the charge leaves, in micro-tokens, MICROS_PER_TOKEN to a token. The debt is 0 while the charge fits the balance.

This is Kafka’s Sensor.record(value, timeMs, true) on a quota’s Rate: the value is recorded first and the quota is checked after, so an over-quota request is charged in full. The part the balance could not cover is debt, and the refill repays it before it adds to the balance, so a client that keeps sending above its quota is throttled by everything it has sent above the bound and not only by its latest request. A caller turns the debt into the throttle time debt / rate, which is Kafka’s QuotaUtils.throttleTime.

A rate-0 bucket has no limit, records nothing and reports no debt.

The debt is not capped, which is right for a byte-rate client quota: its throttle is exactly debt / rate. A quota whose throttle is bounded, or whose debt only ever starves a consumer of the bucket, uses Self::record_bounded.

§Panics

Panics if the injected clock reads more than u64::MAX nanoseconds since its origin, about 584 years.

Source

pub fn record_bounded(&self, tokens: u64, max_wait: Time) -> u64

Charges tokens as Self::record does, and keeps at most max_wait of debt: the debt the refill repays in max_wait at the current rate.

Kafka’s quota sample window forgets what a client recorded about quota.window.num * quota.window.size.seconds ago (SampledStat purges an expired sample), so the debt of an overload does not outlast the window. A debt that grows without a bound outlasts it: a client whose throttle is capped at max_wait would be throttled at the cap on every request until the whole uncapped debt was repaid, and a bucket that grants nothing while in debt would starve its other consumers for as long. The cap bounds the throttle derived from the debt by the wait converted to clock nanoseconds. It rounds credit down to complete micro-tokens, so fractional credit cannot extend debt beyond that wait.

§Panics

Panics if the injected clock reads more than u64::MAX nanoseconds since its origin, about 584 years.

Source

pub fn refund(&self, tokens: u64)

Gives back tokens that an earlier charge took and the caller did not use, capped at the burst.

This is Kafka’s ClientQuotaManager.unrecordQuotaSensor: a fetch that is throttled sends no records, so the bytes it was charged come off the quota again. A rate-0 bucket holds no balance to give back to.

Source

pub fn refund_micros(&self, micros: u64)

Gives back micros micro-tokens that an earlier charge took, capped at the burst. Debt is repaid first. Refunding the effective micro-token charge restores the refilled balance if no debt was discarded by saturation or a cap and no other operation changed the bucket in between.

Source§

impl TokenBucket

Source

pub fn set_token_rate(&self, tokens_per_sec: u64)

Updates the rate in raw tokens per second, with a one-second burst at the new rate. The balance is kept: see Self::set_token_rate_with_burst.

This is the primitive. The bucket counts tokens and does not know what a token means. Callers that meter a dimensioned quantity should use the typed pair that names the dimension: Self::set_byte_rate or Self::set_event_rate.

Source

pub fn set_token_rate_with_burst(&self, new_rate: u64, burst: u64)

Updates the rate and the independent burst capacity, both in raw tokens.

A bucket that already has a rate keeps its balance across the change, as Kafka’s ClientQuotaManager.updateQuotaMetricConfigs only swaps a metric’s bound and leaves what it recorded: the time up to now is refilled at the old rate, the balance is capped at the new burst, and debt stays debt, so a client in debt is still throttled after a change of its quota. A bucket that had no rate has recorded nothing, so it starts at burst.

Source

pub fn token_rate(&self) -> u64

The configured rate in whole raw tokens per second, rounded down. 0 means no limit, or a rate under one token per second: Self::byte_rate reads a fractional rate back exactly.

Source

pub fn token_burst(&self) -> u64

The configured burst capacity in whole raw tokens, rounded down. This is the most the bucket holds.

Source

pub fn set_byte_rate(&self, new_rate: ByteRate)

Updates a byte throughput and bursts one second’s worth, keeping the balance as Self::set_token_rate_with_burst says.

The burst is rate * DEFAULT_BURST_WINDOW. uom type-checks this as a [ByteRate] times a [Time], which gives a [ByteSize].

Source

pub fn set_byte_rate_with_burst(&self, new_rate: ByteRate, burst: ByteSize)

Updates a byte throughput and an independent byte burst capacity.

Both keep their fractional part to a millionth of a byte, so a rate of half a byte per second is enforced as half a byte per second. Kafka holds a byte-rate quota as a double. A positive rate is never stored as the unlimited rate 0. The balance is kept, as Self::set_token_rate_with_burst says.

Source

pub fn byte_rate(&self) -> ByteRate

The configured byte throughput, fractional part included. [krabka_units::prelude::ByteRateExt::ZERO] means no limit.

Source

pub fn runs_at_byte_rate(&self, rate: ByteRate) -> bool

Whether the bucket already runs at rate, as Self::set_byte_rate_with_burst would store it.

A caller that re-applies a configured rate compares with this rather than with Self::byte_rate: a rate finer than a micro-token per second reads back rounded, so a comparison that never matched would reset the bucket on every re-apply.

Source

pub fn byte_burst(&self) -> ByteSize

The configured byte burst capacity, fractional part included.

Source

pub fn set_event_rate(&self, new_rate: Frequency)

Updates an event throughput, such as samples, records, or requests, and bursts one second’s worth.

A token here is one event, not one byte. If you meter events with the byte pair above, the code compiles but the result is wrong. This is why the two pairs are separate.

Source

pub fn set_event_rate_with_burst(&self, new_rate: Frequency, burst: u64)

Updates an event throughput and an independent burst, in whole events.

Source

pub fn event_rate(&self) -> Frequency

The configured event throughput in whole events per second, rounded down. Zero means no limit.

Source§

impl TokenBucket

Source

pub fn new() -> Self

Source

pub fn with_clock(clock: Arc<dyn MonotonicClock>) -> Self

Constructs a bucket backed by a caller-supplied [MonotonicClock].

Production code uses TokenBucket::new, which supplies a [StdMonotonicClock]. Tests pass a [qubit_clock::ManualMonotonicClock], so refill windows advance by an exact, controlled amount instead of by wall-clock sleeping.

Trait Implementations§

Source§

impl Debug for TokenBucket

Source§

fn fmt(&self, __derive_more_f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for TokenBucket

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

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
§

impl<F> FnGhost for F

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> 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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.