You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2014/04/22 14:13:27 UTC

svn commit: r1589103 - in /tomcat/tc7.0.x/trunk: ./ java/org/apache/tomcat/websocket/ test/org/apache/tomcat/websocket/ webapps/docs/

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org