Title: [BUG] KeepAliveScheduler does not evict sessions after ping failure, causing unbounded CPU growth · Issue #1022 · modelcontextprotocol/java-sdk · GitHub
Open Graph Title: [BUG] KeepAliveScheduler does not evict sessions after ping failure, causing unbounded CPU growth · Issue #1022 · modelcontextprotocol/java-sdk
X Title: [BUG] KeepAliveScheduler does not evict sessions after ping failure, causing unbounded CPU growth · Issue #1022 · modelcontextprotocol/java-sdk
Description: Description When keep-alive-interval is configured on a Streamable HTTP MCP Server, KeepAliveScheduler periodically sends ping messages to all registered McpStreamableServerSession instances. When a session's stream is unavailable (clien...
Open Graph Description: Description When keep-alive-interval is configured on a Streamable HTTP MCP Server, KeepAliveScheduler periodically sends ping messages to all registered McpStreamableServerSession instances. When ...
X Description: Description When keep-alive-interval is configured on a Streamable HTTP MCP Server, KeepAliveScheduler periodically sends ping messages to all registered McpStreamableServerSession instances. When ...
Opengraph URL: https://github.com/modelcontextprotocol/java-sdk/issues/1022
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[BUG] KeepAliveScheduler does not evict sessions after ping failure, causing unbounded CPU growth","articleBody":"## Description\n\nWhen `keep-alive-interval` is configured on a Streamable HTTP MCP Server, `KeepAliveScheduler` periodically sends ping messages to all registered `McpStreamableServerSession` instances. When a session's stream is unavailable (client already disconnected), the ping fails and a WARN is logged, but **the session is never removed from the session map**. Over time, dead sessions accumulate indefinitely, causing increasing CPU usage as the scheduler iterates over a growing list of unreachable sessions every interval.\n\n## Environment\n\n- Spring AI: 1.1.4\n- MCP Java SDK: (bundled with Spring AI 1.1.4)\n- Java: JDK 25\n- Server: Tomcat (embedded via Spring Boot 3.4.1)\n- Transport: Streamable HTTP (`spring.ai.mcp.server.protocol=STREAMABLE`)\n\n## Configuration\n\n```yaml\nspring:\n ai:\n mcp:\n server:\n type: SYNC\n protocol: STREAMABLE\n streamable-http:\n mcp-endpoint: /mcp\n keep-alive-interval: 30s\n```\n\n## Steps to Reproduce\n\n1. Deploy an MCP Server with Streamable HTTP transport and `keep-alive-interval: 30s`\n2. Have multiple MCP clients connect, call tools, then disconnect (close TCP)\n3. Wait a few minutes and observe logs and CPU usage\n\n## Observed Behavior\n\nLogs fill with repeated warnings every 30 seconds, one per dead session:\n\n```\nWARN [io.modelcontextprotocol.util.KeepAliveScheduler]\nFailed to send keep-alive ping to session McpStreamableServerSession@3656e950: Stream unavailable for session d0a2a5b9-d427-49f6-8f0b-6c37293e2e35\n\nWARN [io.modelcontextprotocol.util.KeepAliveScheduler]\nFailed to send keep-alive ping to session McpStreamableServerSession@917a226: Stream unavailable for session 5e87f163-f127-4793-9fec-cd35b6a59327\n\nWARN [io.modelcontextprotocol.util.KeepAliveScheduler]\nFailed to send keep-alive ping to session McpStreamableServerSession@5795b3a2: Stream unavailable for session ...\n```\n\n- Dead sessions are **never** removed from the internal map\n- CPU usage grows linearly with the number of accumulated dead sessions\n- The `boundedElastic` thread pool is consumed by failed ping attempts\n- Eventually causes CPU alerts and degrades ability to serve new requests\n\nIn our production environment, CPU went from normal to sustained **135%+** within hours of deployment, triggered by ~50 disconnected clients accumulating dead sessions.\n\n## Expected Behavior\n\nWhen a ping fails (stream unavailable), `KeepAliveScheduler` should:\n\n1. Increment a failure counter for that session\n2. After N consecutive failures (e.g., 2-3), call `session.close()` and remove it from the session map\n3. Log the eviction at INFO level\n\n## Impact\n\n- **Severity: High** — causes progressive CPU degradation in production\n- Combined with the CLOSE-WAIT socket leak issue, can render a server completely unusable\n- No recovery without restart — dead sessions accumulate forever\n\n## Workaround\n\nDisable keep-alive entirely:\n\n```yaml\nspring:\n ai:\n mcp:\n server:\n streamable-http:\n keep-alive-interval: 0s\n```\n\nThis stops the CPU waste but also removes the ability to detect stale sessions proactively.\n\n## Suggested Fix\n\nIn `KeepAliveScheduler`, after a failed ping:\n\n```java\n// Pseudocode\nvoid sendKeepAlive() {\n Iterator\u003cMap.Entry\u003cString, McpStreamableServerSession\u003e\u003e it = sessions.entrySet().iterator();\n while (it.hasNext()) {\n Map.Entry\u003cString, McpStreamableServerSession\u003e entry = it.next();\n try {\n entry.getValue().sendPing();\n failureCounters.remove(entry.getKey());\n } catch (Exception e) {\n int failures = failureCounters.merge(entry.getKey(), 1, Integer::sum);\n if (failures \u003e= MAX_FAILURES) {\n entry.getValue().close();\n it.remove();\n failureCounters.remove(entry.getKey());\n log.info(\"Evicted dead session: {}\", entry.getKey());\n }\n }\n }\n}\n```\n","author":{"url":"https://github.com/lxq19991111","@type":"Person","name":"lxq19991111"},"datePublished":"2026-06-11T04:24:42.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":6},"url":"https://github.com/1022/java-sdk/issues/1022"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:188f81ca-19f7-724e-2c78-aa57473438ec |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | C820:28C0E5:6DAA5E:8EDFCB:6A5C0934 |
| html-safe-nonce | e832cf7592a7134c82b3e2e525f9fb449138b3aab41903690434dac1ab9ca7d1 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDODIwOjI4QzBFNTo2REFBNUU6OEVERkNCOjZBNUMwOTM0IiwidmlzaXRvcl9pZCI6IjY0MTAzNTAyNzAwMTk3OTkzNDgiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 0ea23f1d2d5bde928b82550e9ce1f31628cc98f53af3a302cf7c1c264fe25465 |
| hovercard-subject-tag | issue:4637202780 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/modelcontextprotocol/java-sdk/1022/issue_layout |
| twitter:image | https://opengraph.githubassets.com/22089853800d67873cbfc4484af5667ec6f794a4c7d443e3de6a178b9e0889b7/modelcontextprotocol/java-sdk/issues/1022 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/22089853800d67873cbfc4484af5667ec6f794a4c7d443e3de6a178b9e0889b7/modelcontextprotocol/java-sdk/issues/1022 |
| og:image:alt | Description When keep-alive-interval is configured on a Streamable HTTP MCP Server, KeepAliveScheduler periodically sends ping messages to all registered McpStreamableServerSession instances. When ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | lxq19991111 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| turbo-cache-control | no-preview |
| go-import | github.com/modelcontextprotocol/java-sdk git https://github.com/modelcontextprotocol/java-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 919609219 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/java-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 919609219 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/java-sdk |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width