Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(moka): use Moka per-entry time-based eviction policy #30

Merged
merged 1 commit into from
May 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
}
}