Skip to main content

CachedToken

Struct CachedToken 

Source
pub struct CachedToken<K = ()> { /* private fields */ }
Expand description

Keyed mint-once-reuse with a Clock-driven TTL.

K is what the token varies by: () where an adapter has exactly one (APNs), a push-service origin for VAPID, a service-account id for Google.

The map is bounded at Self::CAPACITY (#136/#137: no unbounded resource on an isolate). A VAPID cache is keyed by push-service origin and a UnifiedPush endpoint is an arbitrary host, so without a bound the map grows for the isolate’s lifetime and never sheds an expired entry.

Implementations§

Source§

impl<K: Eq + Hash + Clone> CachedToken<K>

Source

pub const CAPACITY: usize = 64

How many keys the cache holds at once.

Sized for the realistic key space: one entry for an APNs adapter, one per push-service origin for VAPID (the browsers’ four, plus the UnifiedPush hosts a venture’s users actually chose), one per service account for Google. Past it, the oldest-minted entry is dropped — mint time is what an entry records, and the oldest is the one closest to needing a re-mint anyway. Eviction costs one extra mint, never correctness.

Source

pub fn new(ttl: Duration) -> Self

A cache that re-mints a token once it is older than ttl.

Source

pub fn ttl(&self) -> Duration

How long a token is reused before it is re-minted.

Source

pub fn len(&self) -> usize

How many keys are cached right now, expired entries included until the next insert prunes them.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint.

Source

pub fn is_empty(&self) -> bool

Whether nothing is cached.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint.

Source

pub fn get_or_mint( &self, clock: &dyn Clock, key: &K, mint: impl FnOnce(i64) -> String, ) -> String

The token for key, minting one if there is none or the cached one has aged past the TTL. mint is handed the current UNIX time, which is the same instant the entry is stamped with, so a token’s iat can never disagree with the cache’s idea of its age.

The clock is read once. A clock that goes backwards makes an entry look fresh rather than making its age negative, which is the safe direction: the worst case is one provider rejection, and every caller of this already handles that with Self::invalidate.

mint runs while the lock is held, so a TTL miss mints exactly once however many threads race into it. Dropping the lock first would let two threads both mint and present two provider JWTs to Apple milliseconds apart — TooManyProviderTokenUpdates, the precise failure this cache exists to prevent. Signing takes microseconds and the losing threads want that same token anyway, so the contention is the point. mint must therefore not call back into this cache, and is a plain FnOnce (not a future) so it cannot await while holding the lock.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint — which now runs under the lock, so a panicking mint poisons the cache for the isolate rather than only failing its own send.

Source

pub fn cached(&self, clock: &dyn Clock, key: &K) -> Option<String>

The cached token for key, or None when there is none or it has aged past its lifetime. Nothing is minted.

This is the read half of Self::get_or_mint, for a token that cannot be minted synchronously: Google’s bearer token is exchanged over HTTP, and mint is a plain FnOnce precisely so it can never await while holding the lock. Pair it with Self::store:

ⓘ
if let Some(token) = cache.cached(clock, &key) { return Ok(token); }
let (token, lifetime) = exchange().await?;
cache.store(clock, &key, &token, lifetime);

The lock is therefore not held across the exchange, so two sends racing a cold cache can each exchange one token. That costs a round trip, never correctness: Google issues both and the second store simply wins. It is the trade a synchronous mint does not have to make — and the reason get_or_mint remains the right call wherever minting is local.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint.

Source

pub fn store( &self, clock: &dyn Clock, key: &K, token: impl Into<String>, lifetime: Duration, )

Records token for key, minted now and reusable for lifetime — or for the cache’s own TTL where that is shorter, so the TTL stays a ceiling however long a provider claims its token lives.

A zero lifetime stores nothing that will ever be read back: the entry is stale the instant it lands, which is the honest answer when a provider hands over a token that expires inside the safety margin.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint.

Source

pub fn invalidate(&self, key: &K)

Drops the token for key, so the next Self::get_or_mint re-mints — what an adapter does when the provider says the token expired.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint.

Source

pub fn clear(&self)

Drops every cached token.

§Panics

If the cache mutex was poisoned by a panic inside a previous mint.

Trait Implementations§

Source§

impl<K: Eq + Hash + Clone> Debug for CachedToken<K>

Source§

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

Never prints a token.

Auto Trait Implementations§

§

impl<K = ()> !Freeze for CachedToken<K>

§

impl<K> RefUnwindSafe for CachedToken<K>
where Mutex<HashMap<K, Entry>>: RefUnwindSafe,

§

impl<K> Send for CachedToken<K>
where Mutex<HashMap<K, Entry>>: Send,

§

impl<K> Sync for CachedToken<K>
where Mutex<HashMap<K, Entry>>: Sync,

§

impl<K> Unpin for CachedToken<K>
where Mutex<HashMap<K, Entry>>: Unpin,

§

impl<K> UnsafeUnpin for CachedToken<K>
where Mutex<HashMap<K, Entry>>: UnsafeUnpin,

§

impl<K> UnwindSafe for CachedToken<K>
where Mutex<HashMap<K, Entry>>: 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> 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