use std::sync::Arc;
use dashmap::DashMap;
use indexmap::IndexMap;
use crate::reverse_channel::Upstream;
use crate::session::Session;
#[derive(Debug, Default)]
pub struct SessionManager {
sessions: DashMap<String, Arc<Session>>,
pending: Arc<DashMap<String, tokio::sync::watch::Receiver<()>>>,
}
impl SessionManager {
pub fn new() -> Self {
Self::default()
}
pub fn add(&self, response_id: String, connections: Vec<Upstream>) {
let by_prefix = build_prefix_map(connections);
self.sessions
.insert(response_id, Arc::new(Session::new(by_prefix)));
}
pub fn get(&self, response_id: &str) -> Option<Arc<Session>> {
self.sessions.get(response_id).map(|e| e.value().clone())
}
pub fn begin_initializing(&self, response_id: &str) -> InitGuard {
let (tx, rx) = tokio::sync::watch::channel(());
self.pending.insert(response_id.to_string(), rx);
InitGuard {
pending: Arc::clone(&self.pending),
response_id: response_id.to_string(),
_tx: tx,
}
}
pub fn initializing(
&self,
response_id: &str,
) -> Option<tokio::sync::watch::Receiver<()>> {
self.pending.get(response_id).map(|e| e.value().clone())
}
pub async fn get_or_wait(&self, response_id: &str) -> Option<Arc<Session>> {
loop {
if let Some(session) = self.get(response_id) {
return Some(session);
}
let Some(mut rx) = self.initializing(response_id) else {
return self.get(response_id);
};
while rx.changed().await.is_ok() {}
}
}
pub fn remove(&self, response_id: &str) -> Option<Arc<Session>> {
self.sessions.remove(response_id).map(|(_, session)| session)
}
}
pub struct InitGuard {
pending: Arc<DashMap<String, tokio::sync::watch::Receiver<()>>>,
response_id: String,
_tx: tokio::sync::watch::Sender<()>,
}
impl Drop for InitGuard {
fn drop(&mut self) {
self.pending.remove(&self.response_id);
}
}
pub fn parse_key_env(s: &str) -> Result<Option<[u8; 32]>, String> {
use base64::Engine;
let trimmed = s.trim();
if trimmed.is_empty() {
return Ok(None);
}
let decoded = base64::engine::general_purpose::STANDARD
.decode(trimmed)
.map_err(|e| format!("MCP_ENCRYPTION_KEY: not valid base64: {e}"))?;
let key: [u8; 32] = decoded.try_into().map_err(|got: Vec<u8>| {
format!(
"MCP_ENCRYPTION_KEY: expected 32 bytes after base64-decode, got {}",
got.len(),
)
})?;
Ok(Some(key))
}
fn normalize_prefix_token(s: &str) -> String {
s.replace(['_', '.'], "-")
}
fn build_prefix_map(
mut connections: Vec<Upstream>,
) -> IndexMap<String, Upstream> {
connections.sort_by(|a, b| a.url().cmp(b.url()));
let n = connections.len();
let names: Vec<String> = connections
.iter()
.map(|c| normalize_prefix_token(c.server_name()))
.collect();
let versions: Vec<String> = connections
.iter()
.map(|c| normalize_prefix_token(c.server_version()))
.collect();
let prefix_at = |i: usize, tier: u8| -> String {
match tier {
1 => names[i].clone(),
2 => format!("{}-{}", names[i], versions[i]),
_ => format!("{}-{}-{}", names[i], versions[i], i),
}
};
let mut tier: Vec<u8> = vec![1; n];
loop {
let current: Vec<String> = (0..n).map(|i| prefix_at(i, tier[i])).collect();
let mut counts: std::collections::HashMap<&str, usize> =
std::collections::HashMap::new();
for p in ¤t {
*counts.entry(p.as_str()).or_insert(0) += 1;
}
let mut changed = false;
for i in 0..n {
if counts[current[i].as_str()] > 1 && tier[i] < 3 {
tier[i] += 1;
changed = true;
}
}
if !changed {
break;
}
}
let mut by_prefix: IndexMap<String, Upstream> = IndexMap::with_capacity(n);
for (i, connection) in connections.into_iter().enumerate() {
let key = prefix_at(i, tier[i]);
debug_assert!(
!by_prefix.contains_key(&key),
"duplicate routing prefix after escalation: {key}",
);
by_prefix.insert(key, connection);
}
by_prefix
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn get_or_wait_immediate_hit_and_miss() {
let manager = SessionManager::new();
manager.add("known".to_string(), Vec::new());
assert!(manager.get_or_wait("known").await.is_some());
assert!(manager.get_or_wait("unknown").await.is_none());
}
#[tokio::test]
async fn get_or_wait_parks_until_the_initializer_registers() {
let manager = Arc::new(SessionManager::new());
let guard = manager.begin_initializing("r1");
let waiter = {
let manager = Arc::clone(&manager);
tokio::spawn(async move { manager.get_or_wait("r1").await })
};
tokio::task::yield_now().await;
assert!(!waiter.is_finished());
manager.add("r1".to_string(), Vec::new());
drop(guard);
let session = waiter.await.expect("waiter task");
assert!(session.is_some());
}
#[tokio::test]
async fn get_or_wait_resolves_none_when_the_connect_fails() {
let manager = Arc::new(SessionManager::new());
let guard = manager.begin_initializing("r1");
let waiter = {
let manager = Arc::clone(&manager);
tokio::spawn(async move { manager.get_or_wait("r1").await })
};
tokio::task::yield_now().await;
assert!(!waiter.is_finished());
drop(guard);
assert!(waiter.await.expect("waiter task").is_none());
}
#[tokio::test]
async fn initializing_marker_lives_exactly_as_long_as_the_guard() {
let manager = SessionManager::new();
assert!(manager.initializing("r1").is_none());
let guard = manager.begin_initializing("r1");
assert!(manager.initializing("r1").is_some());
drop(guard);
assert!(manager.initializing("r1").is_none());
}
#[test]
fn parse_key_env_round_trip() {
use base64::Engine;
let key = [0xAAu8; 32];
let env = base64::engine::general_purpose::STANDARD.encode(key);
let parsed = parse_key_env(&env).expect("parse").expect("Some");
assert_eq!(parsed, key);
assert!(parse_key_env("").unwrap().is_none());
assert!(parse_key_env(" ").unwrap().is_none());
assert!(parse_key_env("not-base64!@#").is_err());
let short =
base64::engine::general_purpose::STANDARD.encode(&[0u8; 16][..]);
assert!(parse_key_env(&short).is_err());
}
}