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>
impl<K: Eq + Hash + Clone> CachedToken<K>
Sourcepub const CAPACITY: usize = 64
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.
Sourcepub fn len(&self) -> usize
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.
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Whether nothing is cached.
§Panics
If the cache mutex was poisoned by a panic inside a previous mint.
Sourcepub fn get_or_mint(
&self,
clock: &dyn Clock,
key: &K,
mint: impl FnOnce(i64) -> String,
) -> String
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.
Sourcepub fn cached(&self, clock: &dyn Clock, key: &K) -> Option<String>
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.
Sourcepub fn store(
&self,
clock: &dyn Clock,
key: &K,
token: impl Into<String>,
lifetime: Duration,
)
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.
Sourcepub fn invalidate(&self, key: &K)
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.