Author: markt Date: Tue Apr 23 16:23:20 2013 New Revision: 1471029 URL: http://svn.apache.org/r1471029 Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=54800 Fix memory leaks when using WebSocket client triggered by JVM created threads not having the correct context class loader set.
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1471029&r1=1471028&r2=1471029&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Tue Apr 23 16:23:20 2013 @@ -62,6 +62,8 @@ wsSession.invalidHandlerTypePong=A pong wsSession.removeHandlerFailed=Unable to remove the handler [{0}] as it was not registered with this session wsSession.unknownHandler=Unable to add the message handler [{0}] as it was for the unrecognised type [{1}] +wsWebSocketContainer.asynchronousChannelGroupFail=Unable to create dedicated AsynchronousChannelGroup for WebSocket clients which is required to prevent memory leaks in complex class loader environments like J2EE containers +wsWebSocketContainer.asynchronousSocketChannelFail=Unable to open a connection to the server wsWebSocketContainer.defaultConfiguratorFaill=Failed to create the default configurator wsWebSocketContainer.endpointCreateFail=Failed to create a local endpoint of type [{0}] wsWebSocketContainer.httpRequestFailed=The HTTP request to initiate the WebSocket conenction failed @@ -72,4 +74,5 @@ wsWebSocketContainer.invalidSubProtocol= wsWebSocketContainer.maxBuffer=This implementation limits the maximum size of a buffer to Integer.MAX_VALUE wsWebSocketContainer.missingAnnotation=Cannot use POJO class [{0}] as it is not annotated with @ClientEndpoint wsWebSocketContainer.pathNoHost=No host was specified in URI -wsWebSocketContainer.pathWrongScheme=The scheme [{0}] is not supported \ No newline at end of file +wsWebSocketContainer.pathWrongScheme=The scheme [{0}] is not supported +wsWebSocketContainer.sslEngineFail=Unable to create SSLEngine to support SSL/TLS connections Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1471029&r1=1471028&r2=1471029&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Tue Apr 23 16:23:20 2013 @@ -24,6 +24,7 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousSocketChannel; import java.nio.charset.Charset; import java.security.KeyStore; @@ -40,7 +41,12 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -60,6 +66,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.codec.binary.Base64; import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.util.threads.ThreadPoolExecutor; import org.apache.tomcat.websocket.pojo.PojoEndpointClient; public class WsWebSocketContainer @@ -83,6 +90,43 @@ public class WsWebSocketContainer private static final Random random = new Random(); private static final Charset iso88591 = Charset.forName("ISO-8859-1"); private static final byte[] crlf = new byte[] {13, 10}; + private static final AsynchronousChannelGroup asynchronousChannelGroup; + + static { + AsynchronousChannelGroup result = null; + + // Need to do this with the right thread context class loader else the + // first web app to call this will trigger a leak + ClassLoader original = Thread.currentThread().getContextClassLoader(); + + try { + Thread.currentThread().setContextClassLoader( + AsyncIOThreadFactory.class.getClassLoader()); + + // These are the same settings as the default + // AsynchronousChannelGroup + int initialSize = Runtime.getRuntime().availableProcessors(); + ExecutorService executorService = new ThreadPoolExecutor( + 0, + Integer.MAX_VALUE, + Long.MAX_VALUE, TimeUnit.MILLISECONDS, + new SynchronousQueue<Runnable>(), + new AsyncIOThreadFactory()); + + try { + result = AsynchronousChannelGroup.withCachedThreadPool( + executorService, initialSize); + } catch (IOException e) { + // No good reason for this to happen. + throw new IllegalStateException(sm.getString( + "wsWebSocketContainer.asynchronousChannelGroupFail")); + } + } finally { + Thread.currentThread().setContextClassLoader(original); + } + + asynchronousChannelGroup = result; + } private final Log log = LogFactory.getLog(WsWebSocketContainer.class); private final Map<Class<?>, Set<WsSession>> endpointSessionMap = @@ -231,9 +275,11 @@ public class WsWebSocketContainer AsynchronousSocketChannel socketChannel; try { - socketChannel = AsynchronousSocketChannel.open(); + socketChannel = + AsynchronousSocketChannel.open(asynchronousChannelGroup); } catch (IOException ioe) { - throw new DeploymentException("TODO", ioe); + throw new DeploymentException(sm.getString( + "wsWebSocketContainer.asynchronousSocketChannelFail"), ioe); } Future<Void> fConnect = socketChannel.connect(sa); @@ -597,7 +643,8 @@ public class WsWebSocketContainer return engine; } catch (Exception e) { - throw new DeploymentException("TODO", e); + throw new DeploymentException(sm.getString( + "wsWebSocketContainer.sslEngineFail"), e); } } @@ -704,4 +751,23 @@ public class WsWebSocketContainer public int getProcessPeriod() { return processPeriod; } + + + /** + * Create threads for AsyncIO that have the right context class loader to + * prevent memory leaks. + */ + private static class AsyncIOThreadFactory implements ThreadFactory { + + private AtomicInteger count = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet()); + t.setContextClassLoader(this.getClass().getClassLoader()); + t.setDaemon(true); + return t; + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org