//! Session Manager with backend version tracking //! //! This module implements ProxyAwareSessionManager that integrates with //! ProxyHandler's version control mechanism to automatically invalidate //! sessions when the backend reconnects. //! //! # Architecture //! //! ```text //! ProxyAwareSessionManager //! ├── LocalSessionManager (rmcp 提供的基础实现) //! ├── ProxyHandler (Arc, 访问 backend_version) //! └── DashMap (跟踪 session 创建时的版本) //! //! 工作流程: //! 1. create_session: 记录当前 backend_version //! 2. resume: 检查版本是否匹配 //! - 匹配 → 正常 resume //! - 不匹配 → 返回 NotFound,客户端重新创建 session //! ``` use dashmap::DashMap; use futures::Stream; use rmcp::{ model::{ClientJsonRpcMessage, ServerJsonRpcMessage}, transport::{ WorkerTransport, common::server_side_http::ServerSseMessage, streamable_http_server::session::{ SessionId, SessionManager, local::{LocalSessionManager, LocalSessionManagerError, LocalSessionWorker}, }, }, }; use std::sync::Arc; use std::time::Duration; use tracing::{debug, info, warn}; use super::proxy_handler::ProxyHandler; /// Session 元数据:跟踪 session 创建时的后端版本 #[derive(Debug, Clone)] struct SessionMetadata { backend_version: u64, } /// 感知代理状态的 SessionManager /// /// 职责: /// 1. 委托 LocalSessionManager 处理核心 session 逻辑 /// 2. 维护 session → backend_version 映射 /// 3. 在 resume 时检查版本一致性 /// 4. 版本不匹配时使 session 失效 pub struct ProxyAwareSessionManager { inner: LocalSessionManager, handler: Arc, session_versions: DashMap, } impl ProxyAwareSessionManager { /// 默认 session keep_alive 超时: 30 分钟 /// rmcp 默认 5 分钟太短, 对于代理场景, agent 可能长时间不发消息但仍需要保持 session const DEFAULT_SESSION_KEEP_ALIVE_SECS: u64 = 30 * 60; pub fn new(handler: Arc) -> Self { Self::with_keep_alive(handler, Duration::from_secs(Self::DEFAULT_SESSION_KEEP_ALIVE_SECS)) } pub fn with_keep_alive(handler: Arc, keep_alive: Duration) -> Self { info!( "[Session Manager] Create ProxyAwareSessionManager - MCP ID: {}, keep_alive: {}s", handler.mcp_id(), keep_alive.as_secs() ); let mut inner = LocalSessionManager::default(); inner.session_config.keep_alive = Some(keep_alive); Self { inner, handler, session_versions: DashMap::new(), } } fn check_backend_version(&self, session_id: &SessionId) -> bool { if let Some(meta) = self.session_versions.get(session_id.as_ref()) { let current_version = self.handler.get_backend_version(); if meta.backend_version != current_version { warn!( "[Session version mismatch] session_id={}, creation version={}, current version={}, MCP ID: {}", session_id, meta.backend_version, current_version, self.handler.mcp_id() ); return false; } } true } } // Implement SessionManager trait impl SessionManager for ProxyAwareSessionManager { type Error = LocalSessionManagerError; type Transport = WorkerTransport; async fn create_session(&self) -> Result<(SessionId, Self::Transport), Self::Error> { let (session_id, transport) = self.inner.create_session().await?; let version = self.handler.get_backend_version(); self.session_versions.insert( session_id.to_string(), SessionMetadata { backend_version: version, }, ); info!( "[SessionCreated] session_id={}, backend_version={}, MCP ID: {}", session_id, version, self.handler.mcp_id() ); Ok((session_id, transport)) } async fn initialize_session( &self, id: &SessionId, message: ClientJsonRpcMessage, ) -> Result { if !self.handler.is_backend_available() { warn!( "[Session initialization failed] session_id={}, reason: backend is unavailable, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } if !self.check_backend_version(id) { warn!( "[Session initialization failed] session_id={}, reason: version mismatch, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } debug!( "[Session initialization] session_id={}, MCP ID: {}", id, self.handler.mcp_id() ); self.inner.initialize_session(id, message).await } async fn has_session(&self, id: &SessionId) -> Result { if !self.check_backend_version(id) { return Ok(false); } self.inner.has_session(id).await } async fn close_session(&self, id: &SessionId) -> Result<(), Self::Error> { info!( "[SessionClosed] session_id={}, MCP ID: {}", id, self.handler.mcp_id() ); self.session_versions.remove(id.as_ref()); self.inner.close_session(id).await } async fn create_stream( &self, id: &SessionId, message: ClientJsonRpcMessage, ) -> Result + Send + 'static, Self::Error> { if !self.handler.is_backend_available() { warn!( "[Stream creation failed] session_id={}, reason: backend is unavailable, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } if !self.check_backend_version(id) { warn!( "[Stream creation failed] session_id={}, reason: version mismatch, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } debug!( "[Stream creation] session_id={}, MCP ID: {}", id, self.handler.mcp_id() ); self.inner.create_stream(id, message).await } async fn accept_message( &self, id: &SessionId, message: ClientJsonRpcMessage, ) -> Result<(), Self::Error> { if !self.handler.is_backend_available() { warn!( "[Message rejected] session_id={}, reason: backend unavailable, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } if !self.check_backend_version(id) { warn!( "[Message rejected] session_id={}, reason: version mismatch, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } self.inner.accept_message(id, message).await } async fn create_standalone_stream( &self, id: &SessionId, ) -> Result + Send + 'static, Self::Error> { self.inner.create_standalone_stream(id).await } async fn resume( &self, id: &SessionId, last_event_id: String, ) -> Result + Send + 'static, Self::Error> { // 关键:检查后端版本 if let Some(meta) = self.session_versions.get(id.as_ref()) { let current_version = self.handler.get_backend_version(); if meta.backend_version != current_version { warn!( "[Session recovery failed] session_id={}, reason: backend version change ({} -> {}), MCP ID: {}", id, meta.backend_version, current_version, self.handler.mcp_id() ); // 清理失效 session drop(meta); // 释放 DashMap 的读锁 self.session_versions.remove(id.as_ref()); let _ = self.inner.close_session(id).await; return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } } if !self.handler.is_backend_available() { warn!( "[Session recovery failed] session_id={}, reason: backend is unavailable, MCP ID: {}", id, self.handler.mcp_id() ); return Err(LocalSessionManagerError::SessionNotFound(id.clone())); } debug!( "[SessionResumed] session_id={}, last_event_id={}, MCP ID: {}", id, last_event_id, self.handler.mcp_id() ); self.inner.resume(id, last_event_id).await } }