Describe the bug
rmcp 1.4.0
Sessions created by LocalSessionManager that never receive an initialize request remain alive indefinitely. The session_config.keep_alive timeout is only enforced inside LocalSessionWorker::run() after initialization - sessions waiting in the get initialize request phase have no timeout and are only cleaned up on server shutdown. In production, any HTTP POST to /mcp that creates a session but never follows up with initialize (e.g. a health probe, a load balancer preflight, a client that disconnects immediately, or a test harness bug) causes a permanent session leak. Over time this exhausts memory.
To Reproduce
- Start a
StreamableHttpService with LocalSessionManager.
- Send an HTTP POST to
/mcp with a valid JSON-RPC request that triggers session creation (e.g. initialize), but drop the TCP connection before sending the follow-up initialized notification - or send a non-initialize request that still creates a session
- Observe that the session remains in memory indefinitely
- Only server shutdown cleans it up
Expected behavior
Sessions waiting for initialize should have a configurable timeout (e.g. init_timeout, defaulting to 30-60 seconds). If no initialize request arrives within that window, the session should be terminated.
Logs
Server running with session_config.keep_alive = 1200s (20 min). Four sessions were created at 10:54:10 that never received initialize. They remained alive for 48 minutes until the server was shut down at 11:42:22, at which point they terminated with:
ERROR rmcp::transport::worker: worker quit with fatal: transport terminated, when get initialize request
Meanwhile, sessions that did initialize were correctly reaped after 20 minutes of inactivity.
Additional context
Add a timeout in the pre-init phase of LocalSessionWorker, something like:
let init_timeout = self.session_config.init_timeout.unwrap_or(Duration::from_secs(60));
tokio::select! {
req = self.wait_for_initialize() => { /* proceed */ },
_ = tokio::time::sleep(init_timeout) => {
return Err(WorkerQuitReason::fatal(
LocalSessionWorkerError::InitTimeout(init_timeout),
"get initialize request"
))
}
}
Describe the bug
rmcp 1.4.0
Sessions created by
LocalSessionManagerthat never receive an initialize request remain alive indefinitely. Thesession_config.keep_alivetimeout is only enforced insideLocalSessionWorker::run()after initialization - sessions waiting in theget initialize requestphase have no timeout and are only cleaned up on server shutdown. In production, any HTTP POST to/mcpthat creates a session but never follows up with initialize (e.g. a health probe, a load balancer preflight, a client that disconnects immediately, or a test harness bug) causes a permanent session leak. Over time this exhausts memory.To Reproduce
StreamableHttpServicewithLocalSessionManager./mcpwith a valid JSON-RPC request that triggers session creation (e.g.initialize), but drop the TCP connection before sending the follow-up initialized notification - or send a non-initialize request that still creates a sessionExpected behavior
Sessions waiting for initialize should have a configurable timeout (e.g. init_timeout, defaulting to 30-60 seconds). If no initialize request arrives within that window, the session should be terminated.
Logs
Server running with
session_config.keep_alive = 1200s(20 min). Four sessions were created at 10:54:10 that never receivedinitialize. They remained alive for 48 minutes until the server was shut down at 11:42:22, at which point they terminated with:Meanwhile, sessions that did initialize were correctly reaped after 20 minutes of inactivity.
Additional context
Add a timeout in the pre-init phase of LocalSessionWorker, something like: