https://bz.apache.org/bugzilla/show_bug.cgi?id=70110
Bug ID: 70110
Summary: Memory leak: UpgradeInfo objects not removed from
UpgradeGroupInfo when WebSocket connection cleanup
bypasses normal close path
Product: Tomcat 9
Version: 9.0.x
Hardware: Macintosh
OS: Mac OS X 10.1
Status: NEW
Severity: normal
Priority: P2
Component: WebSocket
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: -----
Created attachment 40188
--> https://bz.apache.org/bugzilla/attachment.cgi?id=40188&action=edit
JUnit test that reproduces the leak by triggering RuntimeException in onOpen()
and verifying UpgradeInfo objects accumulate in UpgradeGroupInfo.upgradeInfos.
**Affects:** Tomcat 9.0.x, 10.x, 11.x (since commit 41497b9883 - October 2020)
**Summary:**
UpgradeInfo objects are never removed from UpgradeGroupInfo.upgradeInfos when
an upgraded
(WebSocket) connection is cleaned up via any path other than the normal close
path in
ConnectionHandler.process(). On long-running instances with heavy WebSocket
traffic, this
causes a permanent memory leak — one UpgradeInfo (~80-100 bytes) per affected
connection.
**Root Cause:**
There are two code paths in AbstractProtocol.ConnectionHandler that call
release(processor)
without first calling httpUpgradeHandler.destroy():
1. **Exception path (lines ~1073-1108):** When processor.process() or
httpUpgradeHandler.init() throws an exception, the catch blocks fall
through to
release(processor) without calling destroy(). The normal close path
correctly calls
destroy() before release(), but the exception path does not.
2. **release(SocketWrapperBase) (lines ~1173-1176):** When
socketWrapper.close() is called
directly by the NIO poller (e.g., on thread pool rejection,
CancelledKeyException, or
endpoint shutdown), it only calls release(processor) — never destroy().
The cleanup chain that removes UpgradeInfo from tracking:
httpUpgradeHandler.destroy()
→ connection.close()
→ upgradeInfo.setGroupInfo(null)
→ UpgradeGroupInfo.removeUpgradeInfo()
This chain is only triggered via destroy(), which is missing in the above
paths.
**Reproduction:**
1. Create a WebSocket endpoint where onOpen() throws a RuntimeException
2. Connect to the endpoint multiple times
3. Observe that UpgradeGroupInfo.upgradeInfos grows with each connection
4. The UpgradeInfo objects are never removed
--
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]