Skip to content

Conversation

@felixweinberger
Copy link
Contributor

@felixweinberger felixweinberger commented Feb 4, 2026

Summary

Adds a session_idle_timeout parameter to StreamableHTTPSessionManager that enables automatic cleanup of idle sessions, fixing the memory leak described in #1283.

Motivation and Context

Sessions created via StreamableHTTPSessionManager persist indefinitely in _server_instances even after clients disconnect without sending DELETE. Over time this leaks memory. This was reported in #1283 and originally addressed in #1159 by @hopeful0 — this PR reworks that approach.

Key design decisions:

  • Uses a CancelScope with a deadline inside each session's run_server task. When the deadline passes, app.run() is cancelled and the session cleans up. No background tasks needed — each session manages its own lifecycle.
  • Incoming requests push the deadline forward, so active sessions are never terminated.
  • When retry_interval (SSE polling) is configured, the effective timeout is at least retry_interval_seconds * 3 to avoid reaping sessions that are simply between polling reconnections.
  • terminate() on the transport is now idempotent (early return if already terminated).
  • Default is None (no timeout, fully backwards compatible). Docstring recommends 1800s (30 min) for most deployments.

How Has This Been Tested?

12 tests in tests/issues/test_1283_idle_session_cleanup.py.

All existing tests pass unchanged.

Breaking Changes

None. session_idle_timeout defaults to None (no timeout).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Based on the approach from PR #1159 by @hopeful0, reworked to use cancel scope deadlines instead of transport-level timeout logic.

Closes #1283

@Kludex
Copy link
Member

Kludex commented Feb 5, 2026

Can't the task manage itself? I don't think we want the overhead of a background task that checks other tasks.

This seems something the specification should mention.

@felixweinberger felixweinberger force-pushed the fweinberger/idle-timeout-termination branch from debfd7d to 73f4cf2 Compare February 5, 2026 10:19
@felixweinberger felixweinberger marked this pull request as draft February 5, 2026 10:23
@felixweinberger felixweinberger force-pushed the fweinberger/idle-timeout-termination branch 7 times, most recently from f8b58ad to 3b38262 Compare February 9, 2026 13:34
Add a session_idle_timeout parameter to StreamableHTTPSessionManager that
enables automatic cleanup of idle sessions, fixing the memory leak
described in #1283.

Uses a CancelScope with a deadline inside each session's run_server task.
When the deadline passes, app.run() is cancelled and the session cleans
up. Incoming requests push the deadline forward. No background tasks
needed — each session manages its own lifecycle.

- terminate() on the transport is now idempotent
- Effective timeout accounts for retry_interval
- Default is None (no timeout, fully backwards compatible)

Github-Issue: #1283
@felixweinberger felixweinberger force-pushed the fweinberger/idle-timeout-termination branch from 3b38262 to 2c87708 Compare February 9, 2026 13:47
- Remove unrelated blank line between logger.info and try block
- Only create CancelScope when session_idle_timeout is set, keeping
  the no-timeout path identical to the original code
- Add docstring to _effective_idle_timeout explaining the 3x
  retry_interval multiplier rationale
The helper silently clamped the user's configured timeout to
retry_interval * 3, which is surprising. Users should set a timeout
that suits their deployment. Updated the docstring to note that the
timeout should comfortably exceed retry_interval when both are set.
@felixweinberger
Copy link
Contributor Author

@Kludex another attempt, no external managers or anything this time.

@felixweinberger
Copy link
Contributor Author

Same thing for main branch: #2022

Comment on lines +59 to +66
session_idle_timeout: Optional idle timeout in seconds for stateful sessions.
If set, sessions that receive no HTTP requests for this
duration will be automatically terminated and removed.
When retry_interval is also configured, ensure the idle
timeout comfortably exceeds the retry interval to avoid
reaping sessions during normal SSE polling gaps.
Default is None (no timeout). A value of 1800
(30 minutes) is recommended for most deployments.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fill 120 characters, and 4 padding from the above line, please.

Suggested change
session_idle_timeout: Optional idle timeout in seconds for stateful sessions.
If set, sessions that receive no HTTP requests for this
duration will be automatically terminated and removed.
When retry_interval is also configured, ensure the idle
timeout comfortably exceeds the retry interval to avoid
reaping sessions during normal SSE polling gaps.
Default is None (no timeout). A value of 1800
(30 minutes) is recommended for most deployments.
session_idle_timeout: Optional idle timeout in seconds for stateful sessions.
if set, sessions that receive no HTTP requests for this
duration will be automatically terminated and removed.
When retry_interval is also configured, ensure the idle
timeout comfortably exceeds the retry interval to avoid
reaping sessions during normal SSE polling gaps.
Default is None (no timeout). A value of 1800
(30 minutes) is recommended for most deployments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping for us to not have this issue based folder, and instead have a structured based on the feature set itself/project structure.

A lot of the tests that AI creates seems unnecessary.

await manager.handle_request(_make_scope(), _mock_receive, _make_send(sent))
session_id = _extract_session_id(sent)

await anyio.sleep(0.3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review your own test suite. And drop sleeps.

@maxisbey maxisbey added the bug Something isn't working label Feb 10, 2026
@maxisbey maxisbey added the P2 Moderate issues affecting some users, edge cases, potentially valuable feature label Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working P2 Moderate issues affecting some users, edge cases, potentially valuable feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants