use std::cell::Cell;
use std::collections::HashSet;
use std::fmt;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use futures_core::Stream;
use rmcp::model::{ClientJsonRpcMessage, ServerJsonRpcMessage};
use rmcp::transport::streamable_http_server::session::{
EventStore, RestoreOutcome, ServerSseMessage, SessionId, SessionManager,
};
pub const SESSION_RETRY_AFTER_S: u64 = 30;
tokio::task_local! {
static REFUSED_SESSION: Cell<bool>;
}
pub async fn scope_refusals<F: std::future::Future>(future: F) -> (F::Output, bool) {
REFUSED_SESSION
.scope(Cell::new(false), async {
let output = future.await;
(output, REFUSED_SESSION.with(Cell::get))
})
.await
}
#[derive(Debug)]
pub enum BoundedError<E> {
LimitReached,
Delegate(E),
}
impl<E: fmt::Display> fmt::Display for BoundedError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BoundedError::LimitReached => f.write_str("session limit reached"),
BoundedError::Delegate(error) => error.fmt(f),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for BoundedError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
BoundedError::LimitReached => None,
BoundedError::Delegate(error) => error.source(),
}
}
}
#[derive(Debug, Default)]
struct Count {
live: HashSet<SessionId>,
pending: usize,
}
#[derive(Debug)]
pub struct BoundedSessions<M> {
inner: M,
max: usize,
count: Mutex<Count>,
}
impl<M> BoundedSessions<M> {
pub fn new(inner: M, max: usize) -> Self {
BoundedSessions {
inner,
max,
count: Mutex::new(Count::default()),
}
}
#[cfg(test)]
pub fn live(&self) -> usize {
let count = self.lock();
count.live.len() + count.pending
}
fn lock(&self) -> MutexGuard<'_, Count> {
self.count.lock().unwrap_or_else(PoisonError::into_inner)
}
}
struct Reservation<'a, M> {
sessions: &'a BoundedSessions<M>,
armed: bool,
}
impl<M> Drop for Reservation<'_, M> {
fn drop(&mut self) {
if self.armed {
let mut count = self.sessions.lock();
count.pending = count.pending.saturating_sub(1);
}
}
}
impl<M: SessionManager> SessionManager for BoundedSessions<M> {
type Error = BoundedError<M::Error>;
type Transport = M::Transport;
async fn create_session(&self) -> Result<(SessionId, Self::Transport), Self::Error> {
{
let mut count = self.lock();
if count.live.len() + count.pending >= self.max {
drop(count);
let _ = REFUSED_SESSION.try_with(|cell| cell.set(true));
return Err(BoundedError::LimitReached);
}
count.pending += 1;
}
let mut reservation = Reservation {
sessions: self,
armed: true,
};
let created = self.inner.create_session().await;
let mut count = self.lock();
count.pending = count.pending.saturating_sub(1);
reservation.armed = false;
match created {
Ok((id, transport)) => {
count.live.insert(id.clone());
Ok((id, transport))
}
Err(error) => Err(BoundedError::Delegate(error)),
}
}
async fn initialize_session(
&self,
id: &SessionId,
message: ClientJsonRpcMessage,
) -> Result<ServerJsonRpcMessage, Self::Error> {
self.inner
.initialize_session(id, message)
.await
.map_err(BoundedError::Delegate)
}
async fn has_session(&self, id: &SessionId) -> Result<bool, Self::Error> {
self.inner
.has_session(id)
.await
.map_err(BoundedError::Delegate)
}
async fn close_session(&self, id: &SessionId) -> Result<(), Self::Error> {
self.lock().live.remove(id);
self.inner
.close_session(id)
.await
.map_err(BoundedError::Delegate)
}
async fn create_stream(
&self,
id: &SessionId,
message: ClientJsonRpcMessage,
) -> Result<impl Stream<Item = ServerSseMessage> + Send + Sync + 'static, Self::Error> {
self.inner
.create_stream(id, message)
.await
.map_err(BoundedError::Delegate)
}
async fn accept_message(
&self,
id: &SessionId,
message: ClientJsonRpcMessage,
) -> Result<(), Self::Error> {
self.inner
.accept_message(id, message)
.await
.map_err(BoundedError::Delegate)
}
async fn create_standalone_stream(
&self,
id: &SessionId,
) -> Result<impl Stream<Item = ServerSseMessage> + Send + Sync + 'static, Self::Error> {
self.inner
.create_standalone_stream(id)
.await
.map_err(BoundedError::Delegate)
}
async fn resume(
&self,
id: &SessionId,
last_event_id: String,
) -> Result<impl Stream<Item = ServerSseMessage> + Send + Sync + 'static, Self::Error> {
self.inner
.resume(id, last_event_id)
.await
.map_err(BoundedError::Delegate)
}
async fn restore_session(
&self,
_id: SessionId,
) -> Result<RestoreOutcome<Self::Transport>, Self::Error> {
Ok(RestoreOutcome::NotSupported)
}
fn event_store(&self) -> Option<Arc<dyn EventStore>> {
self.inner.event_store()
}
}
#[cfg(test)]
mod tests;