Author: markt Date: Tue Apr 22 12:13:26 2014 New Revision: 1589103 URL: http://svn.apache.org/r1589103 Log: Ensure that threads created to support WebSocket clients are stopped when those clients no longer need them. Note that while this happens automatically for WebSocket client calls made by web applications, stand-alone clients must call the Tomcat specific method WsWebSocketContainer.destroy().
Added: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java - copied unchanged from r1589100, tomcat/trunk/java/org/apache/tomcat/websocket/AsyncChannelGroupUtil.java Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1589100,1589102 Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1589103&r1=1589102&r2=1589103&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Tue Apr 22 12:13:26 2014 @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +asyncChannelGroup.createFail=Unable to create dedicated AsynchronousChannelGroup for WebSocket clients which is required to prevent memory leaks in complex class loader environments like J2EE containers + asyncChannelWrapperSecure.closeFail=Failed to close channel cleanly asyncChannelWrapperSecure.concurrentRead=Concurrent read operations are not permitted asyncChannelWrapperSecure.concurrentWrite=Concurrent write operations are not permitted @@ -86,7 +88,6 @@ wsSession.unknownHandlerType=Unable to a # as many as 4 bytes. wsWebSocketContainer.shutdown=The web application is stopping -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}] Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1589103&r1=1589102&r2=1589103&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Tue Apr 22 12:13:26 2014 @@ -42,13 +42,9 @@ 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.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -69,7 +65,6 @@ 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 @@ -109,43 +104,9 @@ public class WsWebSocketContainer StringManager.getManager(Constants.PACKAGE_NAME); private static final Random random = new Random(); 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 AsynchronousChannelGroup asynchronousChannelGroup = null; + private final Object asynchronousChannelGroupLock = new Object(); private final Log log = LogFactory.getLog(WsWebSocketContainer.class); private final Map<Class<?>, Set<WsSession>> endpointSessionMap = @@ -296,8 +257,7 @@ public class WsWebSocketContainer AsynchronousSocketChannel socketChannel; try { - socketChannel = - AsynchronousSocketChannel.open(asynchronousChannelGroup); + socketChannel = AsynchronousSocketChannel.open(getAsynchronousChannelGroup()); } catch (IOException ioe) { throw new DeploymentException(sm.getString( "wsWebSocketContainer.asynchronousSocketChannelFail"), ioe); @@ -830,6 +790,33 @@ public class WsWebSocketContainer "wsWebSocketContainer.sessionCloseFail", session.getId()), ioe); } } + + // Only unregister with AsyncChannelGroupUtil if this instance + // registered with it + if (asynchronousChannelGroup != null) { + synchronized (asynchronousChannelGroupLock) { + if (asynchronousChannelGroup != null) { + AsyncChannelGroupUtil.unregister(); + asynchronousChannelGroup = null; + } + } + } + } + + + private AsynchronousChannelGroup getAsynchronousChannelGroup() { + // Use AsyncChannelGroupUtil to share a common group amongst all + // WebSocket clients + AsynchronousChannelGroup result = asynchronousChannelGroup; + if (result == null) { + synchronized (asynchronousChannelGroupLock) { + if (asynchronousChannelGroup == null) { + asynchronousChannelGroup = AsyncChannelGroupUtil.register(); + } + result = asynchronousChannelGroup; + } + } + return result; } @@ -867,23 +854,4 @@ 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; - } - } } Modified: tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java?rev=1589103&r1=1589102&r2=1589103&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java Tue Apr 22 12:13:26 2014 @@ -111,6 +111,8 @@ public class TestWsWebSocketContainer ex Queue<String> messages = handler.getMessages(); Assert.assertEquals(1, messages.size()); Assert.assertEquals(MESSAGE_STRING_1, messages.peek()); + + ((WsWebSocketContainer) wsContainer).destroy(); } Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1589103&r1=1589102&r2=1589103&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Tue Apr 22 12:13:26 2014 @@ -167,6 +167,12 @@ write call backs can not be destroyed when the web application is stopped. (markt) </add> + <fix> + Ensure that threads created to support WebSocket clients are stopped + when no longer required. This will happen automatically for WebSocket + client connections initiated by web applications but stand alone clients + must call <code>WsWebSocketContainer.destroy()</code>. (markt) + </fix> </changelog> </subsection> <subsection name="Web applications"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org