Skip to main content

Throttle

Struct Throttle 

Source
pub struct Throttle<C: Clock = SystemClock> { /* private fields */ }
Available on crate feature std only.
Expand description

A single outbound throttle backed by a token bucket.

This is the Tier-1 surface: construct one with per_second or per_duration, then pace your outbound work with acquire. Because throttle-net protects downstreams, the headline operation waits for a token rather than rejecting the caller — you are slowing your own requests, not dropping someone else’s. When you would rather not wait, try_acquire reports the outcome immediately.

The bucket refills smoothly and starts full, so a burst up to the capacity is admitted at once and the sustained rate is the refill rate. Token accounting is lock-free (a single atomic compare-and-swap per acquire), and time is read from an injectable ClockSystemClock in production, or a ManualClock in tests via with_clock.

§Examples

use throttle_net::Throttle;

// 100 requests per second, bursting up to 100.
let throttle = Throttle::per_second(100);
throttle.acquire().await?; // returns as soon as a token is free

Implementations§

Source§

impl Throttle<SystemClock>

Source

pub fn per_second(rate: u32) -> Self

Creates a throttle that admits rate units per second, bursting up to rate, driven by the OS monotonic clock.

A rate of 0 yields a throttle that grants nothing; an acquire on it returns ThrottleError::CostExceedsCapacity.

§Examples
use throttle_net::Throttle;

let throttle = Throttle::per_second(50);
assert_eq!(throttle.capacity(), 50);
assert!(throttle.try_acquire());
Source

pub fn per_duration(amount: u32, period: Duration) -> Self

Creates a throttle that admits amount units every period, bursting up to amount, driven by the OS monotonic clock.

Use this when the natural window is not one second — for example, sixty calls per minute, or five per hundred milliseconds.

§Examples
use std::time::Duration;
use throttle_net::Throttle;

// 60 requests per minute.
let throttle = Throttle::per_duration(60, Duration::from_secs(60));
assert_eq!(throttle.capacity(), 60);
Source§

impl<C: Clock> Throttle<C>

Source

pub fn with_clock<C2: Clock>(self, clock: C2) -> Throttle<C2>

Replaces the time source, returning a throttle driven by clock.

The common use is deterministic testing: inject a ManualClock (shared via an Arc) and drive refills by advancing it, with no real sleeping. The bucket is re-anchored to the new clock and starts full.

§Examples
use std::sync::Arc;
use std::time::Duration;
use clock_lib::ManualClock;
use throttle_net::Throttle;

let clock = Arc::new(ManualClock::new());
let throttle = Throttle::per_second(2).with_clock(clock.clone());

assert!(throttle.try_acquire());
assert!(throttle.try_acquire());
assert!(!throttle.try_acquire()); // drained

clock.advance(Duration::from_secs(1)); // a full period refills it
assert!(throttle.try_acquire());
Source

pub fn capacity(&self) -> u32

The maximum number of tokens the throttle can hold (its burst size).

Source

pub fn available(&self) -> u32

The number of whole tokens available right now.

A point-in-time read for observability and tests, not a reservation.

Source

pub fn try_acquire(&self) -> bool

Attempts to take one token without waiting, returning whether it was granted.

§Examples
use throttle_net::Throttle;

let throttle = Throttle::per_second(1);
assert!(throttle.try_acquire());  // the one token
assert!(!throttle.try_acquire()); // none left this instant
Source

pub fn try_acquire_with_cost(&self, cost: u32) -> bool

Attempts to take cost tokens without waiting, returning whether they were granted.

Granting is all-or-nothing: either every token is deducted or none is.

§Examples
use throttle_net::Throttle;

let throttle = Throttle::per_second(10);
assert!(throttle.try_acquire_with_cost(7));
assert!(!throttle.try_acquire_with_cost(7)); // only 3 left
Source

pub fn peek(&self, cost: u32) -> Decision

Reports whether cost tokens would be granted now, without taking them.

This is the non-consuming counterpart to try_acquire_with_cost, used by composite limiters to poll a constituent before committing. The Decision::Retry wait is estimated from the refill rate, so it is a close guide rather than an exact promise.

§Examples
use throttle_net::{Decision, Throttle};

let throttle = Throttle::per_second(4);
assert_eq!(throttle.peek(3), Decision::Acquired); // would grant, took nothing
assert!(throttle.try_acquire_with_cost(4));        // still full
Source§

impl<C: Clock> Throttle<C>

Source

pub async fn acquire(&self) -> Result<(), ThrottleError>

Available on crate feature runtime only.

Takes one token, waiting until one is available.

This is the marquee outbound operation: it paces the caller instead of rejecting it. It returns once a token has been deducted, or ThrottleError::CostExceedsCapacity if the throttle’s capacity is zero.

§Errors

Returns ThrottleError::CostExceedsCapacity when the capacity is 0, because a single token can never be granted.

§Examples
use throttle_net::Throttle;

let throttle = Throttle::per_second(100);
throttle.acquire().await?;
Source

pub async fn acquire_with_cost(&self, cost: u32) -> Result<(), ThrottleError>

Available on crate feature runtime only.

Takes cost tokens, waiting until they are available.

The cost lets one request weigh more than another — a batch of ten, or an LLM call billed by token count. The waiter sleeps for the bucket’s own estimate of the refill time and retries, so it converges without busy spinning even under contention.

§Errors

Returns ThrottleError::CostExceedsCapacity when cost exceeds the throttle’s capacity; that request can never be granted, so it fails fast rather than waiting forever.

§Examples
use throttle_net::Throttle;

let throttle = Throttle::per_second(1000);
throttle.acquire_with_cost(250).await?; // a heavier request

Trait Implementations§

Source§

impl<C: Debug + Clock> Debug for Throttle<C>

Source§

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

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

impl<C: Clock> Limiter for Throttle<C>

Source§

fn peek(&self, cost: u32) -> Decision

Reports whether cost tokens would be granted now, without taking them. Read more
Source§

fn acquire_cost(&self, cost: u32) -> Decision

Attempts to take cost tokens now, deducting them on success. Read more
Source§

fn available(&self) -> u32

Returns the number of whole tokens available right now. Read more
Source§

fn capacity(&self) -> u32

Returns the most tokens this limiter can ever hold at once (its burst ceiling). A request whose cost exceeds this can never be granted.

Auto Trait Implementations§

§

impl<C = SystemClock> !Freeze for Throttle<C>

§

impl<C> RefUnwindSafe for Throttle<C>
where C: RefUnwindSafe,

§

impl<C> Send for Throttle<C>

§

impl<C> Sync for Throttle<C>

§

impl<C> Unpin for Throttle<C>
where C: Unpin,

§

impl<C> UnsafeUnpin for Throttle<C>
where C: UnsafeUnpin,

§

impl<C> UnwindSafe for Throttle<C>
where C: UnwindSafe,

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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.
Source§

impl<E> WithErrorCode<E> for E

Source§

fn with_code(self, code: impl Into<String>) -> CodedError<E>

Attach an error code to an error
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