use crate::mcp_server::ServerRuntime;
use super::SessionId;
use super::SessionStore;
use async_trait::async_trait;
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
pub const DEFAULT_MAX_SESSIONS: usize = 10_000;
const SHARD_COUNT: usize = 16;
fn monotonic_now_ms() -> u64 {
static BASE: OnceLock<Instant> = OnceLock::new();
BASE.get_or_init(Instant::now).elapsed().as_millis() as u64
}
fn now_millis() -> u64 {
monotonic_now_ms()
}
struct SessionEntry {
runtime: Arc<ServerRuntime>,
last_access_ms: AtomicU64,
}
impl SessionEntry {
fn new(runtime: Arc<ServerRuntime>) -> Self {
Self {
runtime,
last_access_ms: AtomicU64::new(now_millis()),
}
}
fn touch(&self) {
self.last_access_ms.store(now_millis(), Ordering::Relaxed);
}
fn is_idle(&self, now_ms: u64, ttl_ms: u64) -> bool {
now_ms.saturating_sub(self.last_access_ms.load(Ordering::Relaxed)) > ttl_ms
}
}
type Shard = RwLock<HashMap<String, SessionEntry>>;
struct Shards {
data: [Shard; SHARD_COUNT],
count: AtomicUsize,
}
#[derive(Clone)]
pub struct InMemorySessionStore {
shards: Arc<Shards>,
max_sessions: usize,
idle_ttl: Option<Duration>,
}
impl Default for InMemorySessionStore {
fn default() -> Self {
Self::with_limits(None, None)
}
}
impl InMemorySessionStore {
pub fn new() -> Self {
Self::default()
}
pub fn with_limits(max_sessions: Option<usize>, idle_ttl: Option<Duration>) -> Self {
Self {
shards: Arc::new(Shards {
data: std::array::from_fn(|_| RwLock::new(HashMap::new())),
count: AtomicUsize::new(0),
}),
max_sessions: max_sessions.unwrap_or(DEFAULT_MAX_SESSIONS),
idle_ttl,
}
}
fn shard(&self, key: &str) -> &Shard {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
&self.shards.data[(hasher.finish() as usize) % SHARD_COUNT]
}
async fn evict_idle(&self) -> usize {
let mut total = 0;
match self.idle_ttl {
Some(ttl) => {
let ttl_ms = ttl.as_millis() as u64;
let now = now_millis();
for shard in self.shards.data.iter() {
let mut guard = shard.write().await;
let before = guard.len();
guard.retain(|_, entry| !entry.is_idle(now, ttl_ms));
total += guard.len();
self.shards
.count
.fetch_sub(before - guard.len(), Ordering::Relaxed);
}
}
None => {
for shard in self.shards.data.iter() {
total += shard.read().await.len();
}
}
}
total
}
}
#[async_trait]
impl SessionStore for InMemorySessionStore {
async fn get(&self, key: &SessionId) -> Option<Arc<ServerRuntime>> {
let shard = self.shard(key).read().await;
let entry = shard.get(key)?;
entry.touch();
Some(entry.runtime.clone())
}
async fn set(&self, key: SessionId, value: Arc<ServerRuntime>) {
let mut shard = self.shard(&key).write().await;
shard.insert(key, SessionEntry::new(value));
self.shards.count.fetch_add(1, Ordering::Relaxed);
}
async fn delete(&self, key: &SessionId) {
let mut shard = self.shard(key).write().await;
if shard.remove(key).is_some() {
self.shards.count.fetch_sub(1, Ordering::Relaxed);
}
}
async fn clear(&self) {
for shard in self.shards.data.iter() {
shard.write().await.clear();
}
self.shards.count.store(0, Ordering::Relaxed);
}
async fn keys(&self) -> Vec<SessionId> {
let mut keys = Vec::new();
for shard in self.shards.data.iter() {
keys.extend(shard.read().await.keys().cloned());
}
keys
}
async fn values(&self) -> Vec<Arc<ServerRuntime>> {
let mut values = Vec::new();
for shard in self.shards.data.iter() {
values.extend(
shard
.read()
.await
.values()
.map(|entry| entry.runtime.clone()),
);
}
values
}
async fn has(&self, session: &SessionId) -> bool {
self.shard(session).read().await.contains_key(session)
}
async fn is_full(&self) -> bool {
if self.shards.count.load(Ordering::Relaxed) < self.max_sessions {
return false;
}
let count = self.evict_idle().await;
count >= self.max_sessions
}
}