You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// kotlinobject:WebSocketConnectionListener {
overridefunonWebSocketConnect(session:Session?) {
session?.close(1008, "I am closing you right away because reason")
}
} // For the full example in context, see code below.
Don't know if it matters, but I am using this "immediately closed WebSocket" because this was the only way I managed to pass a close reasong (a string, for instance) to the client. Any other ways (e.g. throwing an exception) would terminate the connection with status 1006 and no reason.
Root cause analysis
I have tried to inspect the code a bit to find out what could be causing the leak. From what I could understand, the problem is the SessionTracker not removing the sessions for the "immediately-closed-WebSockets" (ICWS) due to a race condition between its session opened and session closed methods:
I have managed to contain the leak by registering an aditional WebSocketSessionListener that, for every opened session, checks if it is closed. If it is, the listener then removes the session from SessionTracker#sessions via reflection.
Workaround code is commented out in the snippet below.
How to reproduce?
// kotlinimportjakarta.servlet.ServletContextimportorg.eclipse.jetty.server.Serverimportorg.eclipse.jetty.server.ServerConnectorimportorg.eclipse.jetty.servlet.ServletContextHandlerimportorg.eclipse.jetty.websocket.api.Sessionimportorg.eclipse.jetty.websocket.api.WebSocketConnectionListenerimportorg.eclipse.jetty.websocket.server.JettyWebSocketServerContainerimportorg.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializerfunmain() {
val server =Server()
val connector =ServerConnector(server)
connector.port =8080
server.addConnector(connector)
val context =ServletContextHandler(ServletContextHandler.SESSIONS)
context.contextPath ="/"
server.handler = context
JettyWebSocketServletContainerInitializer.configure(context) { _:ServletContext?, wsContainer:JettyWebSocketServerContainer->
wsContainer.addMapping("/ws") { _, _ ->object:WebSocketConnectionListener {
overridefunonWebSocketConnect(session:Session?) {
session?.close(1008, "I am a WS that closes immediately")
}
}
}
// workaround// wsContainer.addSessionListener(object : WebSocketSessionListener {// override fun onWebSocketSessionOpened(session: Session?) {// if (!session!!.isOpen) {// val sessionTracker = wsContainer.javaClass.getDeclaredField("sessionTracker")// sessionTracker.isAccessible = true// (sessionTracker.get(wsContainer) as SessionTracker).onWebSocketSessionClosed(session)// }// }// })
}
server.start()
server.join()
}
To use the workaround, uncomment the code and run again. Notice the sessions aren't stacked up anymore.
The code is in Kotlin just because it was the language I was using and it was simpler for me to elaborate the snippet. If you want, let me know and I can convert it to Java.
The text was updated successfully, but these errors were encountered:
Jetty version(s)
Tested on: 11.0.3 and 11.0.6
Java version/vendor
(use: java -version)
Tested on:
OS type/version
Tested on: Windows 10, Linux Alpine
Description
Opening a WebSocket that immediately closes leaves a
session
instance indefinitely onorg.eclipse.jetty.websocket.common.SessionTracker#sessions.
By "a WebSocket that immediately closes" I mean a
WebSocketConnectionListener
that closes the WebSocket on theonWebSocketConnect
method. Example:Don't know if it matters, but I am using this "immediately closed WebSocket" because this was the only way I managed to pass a close reasong (a string, for instance) to the client. Any other ways (e.g. throwing an exception) would terminate the connection with status 1006 and no reason.
Root cause analysis
I have tried to inspect the code a bit to find out what could be causing the leak. From what I could understand, the problem is the
SessionTracker
not removing thesession
s for the "immediately-closed-WebSockets" (ICWS) due to a race condition between its session opened and session closed methods:SessionTracker
listens to all WebSocket session events (it implementsWebSocketSessionListener
)SessionTracker#onWebSocketSessionOpened()
andSessionTracker#onWebSocketSessionClosed()
you will notice that, for the ICWS, they are called out of order, that is, for the samesession
instance, firstonWebSocketSessionClosed()
is called thenonWebSocketSessionOpened()
is, thus leaving thesession
in thesessions
set indefinitely.Workaround
I have managed to contain the leak by registering an aditional
WebSocketSessionListener
that, for every opened session, checks if it is closed. If it is, the listener then removes the session fromSessionTracker#sessions
via reflection.Workaround code is commented out in the snippet below.
How to reproduce?
Run
main()
above and open many connections:By inspecting org.eclipse.jetty.websocket.common.SessionTracker#sessions, you'll see the sessions are held even after whe WebSockets are closed (to inspect it, put a breakpoint on
SessionTracker#onWebSocketSessionOpened
orSessionTracker#onWebSocketSessionClosed
and try to connect a ws).To use the workaround, uncomment the code and run again. Notice the sessions aren't stacked up anymore.
The code is in Kotlin just because it was the language I was using and it was simpler for me to elaborate the snippet. If you want, let me know and I can convert it to Java.
The text was updated successfully, but these errors were encountered: