Skip to content

Commit

Permalink
feat(moka): use Moka per-entry time-based eviction policy (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
arcstur committed May 5, 2024
1 parent 9c7522e commit 318ba06
Showing 1 changed file with 56 additions and 12 deletions.
68 changes: 56 additions & 12 deletions moka-store/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use std::time::{Duration as StdDuration, Instant as StdInstant};

use async_trait::async_trait;
use moka::future::Cache;
use moka::{future::Cache, Expiry};
use time::OffsetDateTime;
use tower_sessions_core::{
session::{Id, Record},
session_store, SessionStore,
};

/// A session store that uses Moka, a fast and concurrent caching library.
///
/// This store uses Moka's built-in time-based per-entry expiration policy
/// according to the session's expiry date. Therefore, expired sessions
/// are automatically removed from the cache.
#[derive(Debug, Clone)]
pub struct MokaStore {
cache: Cache<Id, Record>,
Expand All @@ -28,7 +34,8 @@ impl MokaStore {
let cache_builder = match max_capacity {
Some(capacity) => Cache::builder().max_capacity(capacity),
None => Cache::builder(),
};
}
.expire_after(SessionExpiry);

Self {
cache: cache_builder.build(),
Expand All @@ -52,11 +59,9 @@ impl SessionStore for MokaStore {
}

async fn load(&self, session_id: &Id) -> session_store::Result<Option<Record>> {
Ok(self
.cache
.get(session_id)
.await
.filter(|Record { expiry_date, .. }| is_active(*expiry_date)))
// expired sessions are automatically removed from the cache,
// so it's safe to just call get
Ok(self.cache.get(session_id).await)
}

async fn delete(&self, session_id: &Id) -> session_store::Result<()> {
Expand All @@ -65,9 +70,48 @@ impl SessionStore for MokaStore {
}
}

// TODO: Moka supports expiry natively, but that interface is being overhauled
// such that it's more accessible. When that work is done, we should replace
// this with actual expiry.
fn is_active(expiry_date: OffsetDateTime) -> bool {
expiry_date > OffsetDateTime::now_utc()
/// Moka per-entry expiration policy for session records.
struct SessionExpiry;

impl SessionExpiry {
/// Calculates the expiry duration of a record
/// by comparing it to the current time.
///
/// If the expiry date of the record is in the past,
/// returns an empty duration.
fn expiry_date_to_duration(record: &Record) -> StdDuration {
// we use this to calculate the current time
// because it is not possible to convert
// StdInstant to OffsetDateTime
let now = OffsetDateTime::now_utc();
let expiry_date = record.expiry_date;

if expiry_date > now {
(expiry_date - now).unsigned_abs()
} else {
StdDuration::default()
}
}
}

impl Expiry<Id, Record> for SessionExpiry {
fn expire_after_create(
&self,
_id: &Id,
record: &Record,
_created_at: StdInstant,
) -> Option<StdDuration> {
Some(Self::expiry_date_to_duration(record))
}

fn expire_after_update(
&self,
_id: &Id,
record: &Record,
_updated_at: StdInstant,
_duration_until_expiry: Option<StdDuration>,
) -> Option<StdDuration> {
// expiry_date could change, so we calculate it again
Some(Self::expiry_date_to_duration(record))
}
}

0 comments on commit 318ba06

Please sign in to comment.