pub(super) struct AtomicTokenBucket {
available: AtomicU64,
capacity: u64,
}Expand description
The atomic token bucket minus the clock component.
This is lock-free and the owner needs to refill it with a specific number of tokens it wants to be distributed across many actors.
Fields§
§available: AtomicU64Every access to these counters is with Ordering::Relaxed as they don’t
protect any outside data and so we only care about the atomicity action on the
counter.
The current token count.
capacity: u64The maximum number of tokens the bucket may hold a.k.a the burst.
This is atomic because it can be set during runtime. For instance, a config option reconfigure of a bandwidth rate.
Implementations§
Source§impl AtomicTokenBucket
impl AtomicTokenBucket
Sourcepub(super) fn new(capacity: u64) -> Self
pub(super) fn new(capacity: u64) -> Self
A new bucket capped at capacity tokens.
It starts full.
Sourcepub(super) fn claim(&self, tokens: u64) -> Option<u64>
pub(super) fn claim(&self, tokens: u64) -> Option<u64>
Claim all tokens from the bucket or nothing if not enough available.
The request is first clamped to the capacity because the bucket can never hold more than its burst.
Returns Some(claimed) if the bucket held at least the clamped request which indicates
that they are now granted to the caller. Note that claimed can be lower than tokens
hence why the caller needs to use that value and not what it asked for.
Returns None otherwise, nothing is taken.
Sourcepub(super) fn refill(&self, tokens: u64)
pub(super) fn refill(&self, tokens: u64)
Add tokens to the bucket which is capped at the capacity.
We use a CAS loop (Compare-And-Swap) because we need to cap the refill to the internal capacity atomically. A fetch_add + fetch_sub is not possible in order to refill atomically because of this race:
capacity = 100, available = 90:
refill: fetch_add(50) -> available = 140 (above capacity)
claim: fetch_sub(140) -> available = 0 (illegal claim, above capacity)
refill: fetch_sub(40) -> available underflows (adjust overshoot too late)Any tokens that overshoot the capacity are forfeited.
Finally, the CAS loop is considered ok because the refill is not in the fast path and should work most of the time on the first iteration.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for AtomicTokenBucket
impl RefUnwindSafe for AtomicTokenBucket
impl Send for AtomicTokenBucket
impl Sync for AtomicTokenBucket
impl Unpin for AtomicTokenBucket
impl UnsafeUnpin for AtomicTokenBucket
impl UnwindSafe for AtomicTokenBucket
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more