Hello Mark,
Please let me disagree with you. I have connected JVisualVM to Tomcat JVM,
run the test with 10,000 connections, performed several explicit GCs and
made a heap dump. All 10,000 WsSessions are in heap. They are referenced as
following:
this
<- wsSession - class:
org.apache.tomcat.websocket.server.WsHttpUpgradeHandler
<- internalHttpUpgradeHandler - class:
org.apache.coyote.http11.upgrade.UpgradeProcessorInternal
<- val - class: java.util.concurrent.ConcurrentHashMap$Node
<- [12930] - class: java.util.concurrent.ConcurrentHashMap$Node[]
<- table - class: java.util.concurrent.ConcurrentHashMap
<- connections - class:
org.apache.coyote.AbstractProtocol$ConnectionHandler
<- handler - class: org.apache.tomcat.util.net.NioEndpoint
<- this$0 (Java frame) - class:
org.apache.tomcat.util.net.NioEndpoint$Acceptor
Do you think it might be Tomcat configuration dependent? Please see my
server.xml below.
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener"
SSLEngine="on" />
<Listener
className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener
className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener
className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
asyncTimeout="20000"
URIEncoding="utf-8"
acceptorThreadCount="2"
compressableMimeType="text/html,text/xml,text/plain,text/x-json,application/javascript,application/json,text/css"
compression="on"
connectionTimeout="60000"
maxThreads="1024"
processorCache="512"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r"
"%{User-Agent}i" %s %b %Dms" resolveHosts="false"/>
</Host>
</Engine>
</Service>
</Server>
Thank you,
Kirill
On Tue, 30 Jul 2019 at 21:45, Mark Thomas <[email protected]> wrote:
> On 30/07/2019 05:48, Kirill Ilyukhin wrote:
> > Hello Mark,
> >
> > Please see the test case and Tomcat JVM heap dump screenshot attached.
> > For sake of simplicity I do Thread.sleep() in client code instead of
> > reading bytes from server.
>
> That test case does not demonstrate a memory leak in either Tomcat 9.0.x
> nor Tomcat 8.5.x.
>
> There are some WsSession instances in memory at the end of the test but
> they are unreachable - i.e. eligible for GC.
>
> Mark
>
>
> > Test configuration is the following:
> >
> > Server version: Apache Tomcat/8.5.3
> > Server built: Jun 9 2016 11:16:29 UTC
> > Server number: 8.5.3.0
> > OS Name: Mac OS X
> > OS Version: 10.14.5
> > Architecture: x86_64
> > JVM Version: 1.8.0_112-b16
> > JVM Vendor: Oracle Corporation
> > The APR based Apache Tomcat Native library which allows optimal
> > performance in production environments was not found on the
> > java.library.path: ...
> >
> > Thank you,
> > Kirill
> >
> > On Tue, 30 Jul 2019 at 02:15, Mark Thomas <[email protected]
> > <mailto:[email protected]>> wrote:
> >
> > On 26/07/2019 10:33, Kirill Ilyukhin wrote:
> > > Hello,
> > >
> > > When Tomcat receives WebSocket text message with invalid UTF-8, it
> > closes
> > > this connection with NOT_CONSISTENT reason. But after that some
> > objects
> > > (WsSession, UpgradeHandler, etc) stay in heap forever. They are
> > referenced
> > > from AbstractProtocol's connections map.
> > >
> > > This leak consistently happens with Tomcat 8.5.3 and 8.5.43, both
> > on Mac OS
> > > and Windows, with or without Tomcat native.
> > >
> > > I have created a very simple WebSocket Endpoint which does nothing
> > except
> > > logging its events and incoming messages, please see the code
> > below. Also
> > > you will need a WebSocket client which sends broken UTF-8 in text
> > message
> > > right after connecting to the server.
> >
> > I can't repeat this with either 9.0.x nor 8.5.x. I've repeated the
> steps
> > described above and checked the resulting state with a profiler. No
> > references are retained to WsSession objects nor WsHttpUpgradeHandler
> > objects.
> >
> > You'll need to provide the simplest possible test case (single class
> > client, simplest possible WAR) that demonstrates the issue.
> >
> > Mark
> >
> >
> > >
> > > Thank you,
> > > Kirill
> > >
> > > -----------------------
> > > package com.example.wstest;
> > >
> > > import org.apache.log4j.Logger;
> > > import javax.websocket.*;
> > >
> > > public class WSEndpoint extends Endpoint {
> > > private static final Logger logger =
> > Logger.getLogger(WSEndpoint.class);
> > > private WSConnection connection;
> > >
> > > @Override
> > > public void onOpen(Session session, EndpointConfig config) {
> > > connection = new WSConnection(session);
> > > logger.info <http://logger.info>("Opened WebSocket
> > session-" + session.getId());
> > > }
> > >
> > > @Override
> > > public void onClose(Session session, CloseReason closeReason) {
> > > logger.info <http://logger.info>("Closed WebSocket
> > session-" + session.getId() + ",
> > > reason: " + closeReason.getCloseCode() + " (" +
> > > closeReason.getReasonPhrase() + ")");
> > > connection.destroy();
> > > connection = null;
> > > }
> > >
> > > @Override
> > > public void onError(Session session, Throwable throwable) {
> > > logger.info <http://logger.info>("Error on WebSocket
> > session-" + session.getId(),
> > > throwable);
> > > connection.destroy();
> > > connection = null;
> > > }
> > >
> > > static class WSConnection implements
> > MessageHandler.Whole<String> {
> > > private final Session session;
> > >
> > > WSConnection(Session session) {
> > > this.session = session;
> > > session.addMessageHandler(this);
> > > }
> > >
> > > public void destroy() {
> > > session.removeMessageHandler(this);
> > > }
> > >
> > > @Override
> > > public void onMessage(String message) {
> > > logger.info <http://logger.info>("Session-" +
> > session.getId() + " onMessage(" +
> > > message +")");
> > > }
> > > }
> > > }
> > > -----------------------
> > >
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: [email protected]
> > <mailto:[email protected]>
> > For additional commands, e-mail: [email protected]
> > <mailto:[email protected]>
> >
> >
> >
> > ---------------------------------------------------------------------
> > To unsubscribe, e-mail: [email protected]
> > For additional commands, e-mail: [email protected]
> >
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>
>