pub struct Bucket<C: Clock = SystemClock> { /* private fields */ }Expand description
A token bucket: a counter that refills over time and grants tokens on demand.
A bucket holds up to its capacity in tokens, accrues more at a fixed rate,
and hands them out when asked. The hot path is lock-free — a single
compare_exchange_weak on a packed atomic word — and allocation-free.
Refill is lazy: there is no background thread or timer, the token count
is brought current from the monotonic clock the instant you call
acquire, try_acquire, or
available.
The type parameter C is the time source. It defaults to
SystemClock (the OS monotonic clock); inject a
ManualClock with with_clock
to drive time by hand in tests. Bucket is Send + Sync whenever its clock
is, which every Clock implementation guarantees.
§Limits
The packed representation caps capacity at about 4.29 million tokens
(u32::MAX millitokens). Time is tracked as 32-bit milliseconds since
construction and wraps every ~49.7 days; the wrap is handled, so an
actively-used bucket refills correctly indefinitely (only a bucket idle for
longer than that may under-refill once, safely, on its next use).
§Examples
The one-line common case:
use better_bucket::Bucket;
let bucket = Bucket::per_second(100);
if bucket.try_acquire(1) {
// allowed — do the work
}Implementations§
Source§impl Bucket<SystemClock>
impl Bucket<SystemClock>
Sourcepub fn per_second(rate: u32) -> Self
pub fn per_second(rate: u32) -> Self
Creates a bucket of capacity rate that refills rate tokens per
second, starting full, driven by the OS monotonic clock.
This is the headline Tier-1 constructor. A rate of 0 yields a bucket
that grants nothing (capacity 0); use BucketConfig::new when you
want zero rejected as an error.
§Examples
use better_bucket::Bucket;
let bucket = Bucket::per_second(50);
assert_eq!(bucket.capacity(), 50);
assert!(bucket.try_acquire(1));Sourcepub fn per_duration(amount: u32, period: Duration) -> Self
pub fn per_duration(amount: u32, period: Duration) -> Self
Creates a bucket of capacity amount that refills amount tokens every
period, starting full, driven by the OS monotonic clock.
Use this when the natural rate is not per-second — e.g. 5 tokens per 100
milliseconds, or 1000 per minute. An amount of 0 or a zero period
yields a bucket that grants nothing.
§Examples
use better_bucket::Bucket;
use std::time::Duration;
// 5 tokens every 100ms.
let bucket = Bucket::per_duration(5, Duration::from_millis(100));
assert_eq!(bucket.capacity(), 5);Sourcepub fn from_config(config: BucketConfig) -> Self
pub fn from_config(config: BucketConfig) -> Self
Creates a bucket from a validated BucketConfig, driven by the OS
monotonic clock.
Use this when you need full control over capacity, rate, and initial fill independently (e.g. a large burst ceiling with a slow refill, or a bucket that starts empty).
§Examples
use better_bucket::{Bucket, BucketConfig};
use std::time::Duration;
// 500-token burst, 100/sec refill, starting empty.
let config = BucketConfig::new(500, 100, Duration::from_secs(1), 0)?;
let bucket = Bucket::from_config(config);
assert_eq!(bucket.available(), 0);Source§impl<C: Clock> Bucket<C>
impl<C: Clock> Bucket<C>
Sourcepub fn with_clock<C2: Clock>(self, clock: C2) -> Bucket<C2>
pub fn with_clock<C2: Clock>(self, clock: C2) -> Bucket<C2>
Replaces the bucket’s time source, resetting it to its initial fill anchored at the new clock’s current reading.
This is the clock-injection seam. The intended use is immediately after
construction — chiefly in tests, where injecting a
ManualClock makes refill behaviour
deterministic with no sleep.
§Examples
use better_bucket::Bucket;
use clock_lib::ManualClock;
use std::sync::Arc;
use std::time::Duration;
let clock = Arc::new(ManualClock::new());
let bucket = Bucket::per_second(10).with_clock(Arc::clone(&clock));
assert!(bucket.try_acquire(10)); // drain it
assert!(!bucket.try_acquire(1)); // empty
clock.advance(Duration::from_secs(1)); // no real sleep
assert_eq!(bucket.available(), 10); // fully refilledSourcepub fn acquire(&self, n: u32) -> Decision
pub fn acquire(&self, n: u32) -> Decision
Attempts to take n tokens, returning the full Decision.
Brings the bucket current (lazy refill) and, if at least n tokens are
available, deducts them and returns Decision::Allowed. Otherwise the
bucket is left untouched and Decision::Denied carries the minimum
wait until the request would succeed. Requesting 0 always succeeds;
requesting more than the capacity can never succeed (the denial’s
retry_after is Duration::MAX).
This never blocks and never allocates.
§Examples
use better_bucket::{Bucket, Decision};
let bucket = Bucket::per_second(5);
assert_eq!(bucket.acquire(3), Decision::Allowed);
assert_eq!(bucket.available(), 2);Sourcepub fn try_acquire(&self, n: u32) -> bool
pub fn try_acquire(&self, n: u32) -> bool
Attempts to take n tokens, returning whether it succeeded.
The one-line convenience over acquire: equivalent to
self.acquire(n).is_allowed(), for the common case where you only need
allow/deny and not the retry hint.
§Examples
use better_bucket::Bucket;
let bucket = Bucket::per_second(1);
assert!(bucket.try_acquire(1));
assert!(!bucket.try_acquire(1)); // drainedSourcepub fn available(&self) -> u32
pub fn available(&self) -> u32
Returns how many whole tokens are available right now, after lazy refill.
This is a momentary snapshot; under concurrent acquires it can be stale the instant it returns. Treat it as advisory.
§Examples
use better_bucket::Bucket;
let bucket = Bucket::per_second(10);
assert_eq!(bucket.available(), 10);
assert!(bucket.try_acquire(4));
assert_eq!(bucket.available(), 6);Sourcepub const fn capacity(&self) -> u32
pub const fn capacity(&self) -> u32
Returns the bucket’s capacity (its burst ceiling), in whole tokens.
§Examples
use better_bucket::Bucket;
assert_eq!(Bucket::per_second(64).capacity(), 64);Sourcepub const fn config(&self) -> BucketConfig
pub const fn config(&self) -> BucketConfig
Returns the configuration this bucket was built from.
§Examples
use better_bucket::Bucket;
use std::time::Duration;
let bucket = Bucket::per_second(10);
assert_eq!(bucket.config().refill_period(), Duration::from_secs(1));Sourcepub fn reset(&self)
pub fn reset(&self)
Refills the bucket to full and marks it current as of now.
Use it to discard accumulated debt and grant a fresh burst — for example at the start of a new billing window, or to clear a backlog after a dependency recovers. Long uptime needs no special handling: the millisecond counter wraps safely, so an actively-used bucket never stalls.
§Examples
use better_bucket::Bucket;
let bucket = Bucket::per_second(4);
assert!(bucket.try_acquire(4));
assert_eq!(bucket.available(), 0);
bucket.reset();
assert_eq!(bucket.available(), 4);Source§impl Bucket<SystemClock>
impl Bucket<SystemClock>
Sourcepub fn builder() -> BucketBuilder
pub fn builder() -> BucketBuilder
Starts a BucketBuilder for explicit configuration.
The Tier-2 entry point, for when per_second /
per_duration are not enough — e.g. a capacity
and refill rate that differ, or a non-full initial fill.
§Examples
use better_bucket::Bucket;
use std::time::Duration;
let bucket = Bucket::builder()
.capacity(500)
.refill(100, Duration::from_secs(1))
.build()?;Trait Implementations§
Source§impl<C: Clock> TokenBucket for Bucket<C>
impl<C: Clock> TokenBucket for Bucket<C>
Source§fn try_acquire(&self, n: u32) -> bool
fn try_acquire(&self, n: u32) -> bool
n tokens, returning whether it succeeded.