Author: markt Date: Thu Sep 19 11:10:06 2013 New Revision: 1524694 URL: http://svn.apache.org/r1524694 Log: More robust solution to the problem of blocking writes not be closed when the web application stops. Futures used for blocking writes are registered with the session and the session completes them with an exception if they are outstanding when the session closes.
Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/FutureToSendHandler.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsContextListener.java tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsSci.java Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1524687 Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/FutureToSendHandler.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/FutureToSendHandler.java?rev=1524694&r1=1524693&r2=1524694&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/FutureToSendHandler.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/FutureToSendHandler.java Thu Sep 19 11:10:06 2013 @@ -31,12 +31,19 @@ import javax.websocket.SendResult; class FutureToSendHandler implements Future<Void>, SendHandler { private final CountDownLatch latch = new CountDownLatch(1); + private final WsSession wsSession; private volatile SendResult result = null; + public FutureToSendHandler(WsSession wsSession) { + this.wsSession = wsSession; + } + + // --------------------------------------------------------- SendHandler @Override public void onResult(SendResult result) { + this.result = result; latch.countDown(); } @@ -64,7 +71,12 @@ class FutureToSendHandler implements Fut @Override public Void get() throws InterruptedException, ExecutionException { - latch.await(); + try { + wsSession.registerFuture(this); + latch.await(); + } finally { + wsSession.unregisterFuture(this); + } if (result.getException() != null) { throw new ExecutionException(result.getException()); } @@ -75,7 +87,14 @@ class FutureToSendHandler implements Fut public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - boolean retval = latch.await(timeout, unit); + boolean retval = false; + try { + wsSession.registerFuture(this); + retval = latch.await(timeout, unit); + } finally { + wsSession.unregisterFuture(this); + + } if (retval == false) { throw new TimeoutException(); } 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=1524694&r1=1524693&r2=1524694&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 Thu Sep 19 11:10:06 2013 @@ -71,12 +71,19 @@ wsSession.closed=The WebSocket session h wsSession.duplicateHandlerBinary=A binary message handler has already been configured wsSession.duplicateHandlerPong=A pong message handler has already been configured wsSession.duplicateHandlerText=A text message handler has already been configured -wsSession.sendCloseFail=Failed to send close message to remote endpoint wsSession.invalidHandlerTypePong=A pong message handler must implement MessageHandler.Basic +wsSession.messageFailed=Unable to write the complete message as the WebSocket connection has been closed +wsSession.sendCloseFail=Failed to send close message to remote endpoint 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}] wsSession.unknownHandlerType=Unable to add the message handler [{0}] as it was wrapped as the unrecognised type [{1}] +# Note the following message is used as a close reason in a WebSocket control +# frame and therefore must be 123 bytes (not characters) or less in length. +# Messages are encoded using UTF-8 where a single character may be encoded in +# 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 @@ -90,4 +97,5 @@ wsWebSocketContainer.maxBuffer=This impl 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 +wsWebSocketContainer.sessionCloseFail=Session with ID [{0}] did not close cleanly wsWebSocketContainer.sslEngineFail=Unable to create SSLEngine to support SSL/TLS connections Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java?rev=1524694&r1=1524693&r2=1524694&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java Thu Sep 19 11:10:06 2013 @@ -119,7 +119,7 @@ public abstract class WsRemoteEndpointIm public Future<Void> sendBytesByFuture(ByteBuffer data) { - FutureToSendHandler f2sh = new FutureToSendHandler(); + FutureToSendHandler f2sh = new FutureToSendHandler(wsSession); sendBytesByCompletion(data, f2sh); return f2sh; } @@ -156,7 +156,7 @@ public abstract class WsRemoteEndpointIm public Future<Void> sendStringByFuture(String text) { - FutureToSendHandler f2sh = new FutureToSendHandler(); + FutureToSendHandler f2sh = new FutureToSendHandler(wsSession); sendStringByCompletion(text, f2sh); return f2sh; } @@ -191,7 +191,7 @@ public abstract class WsRemoteEndpointIm // trigger a session close and depending on timing the client // session may close before we can read the timeout. long timeout = getBlockingSendTimeout(); - FutureToSendHandler f2sh = new FutureToSendHandler(); + FutureToSendHandler f2sh = new FutureToSendHandler(wsSession); TextMessageSendHandler tmsh = new TextMessageSendHandler(f2sh, part, last, encoder, encoderBuffer, this); tmsh.write(); @@ -216,7 +216,7 @@ public abstract class WsRemoteEndpointIm // trigger a session close and depending on timing the client // session may close before we can read the timeout. long timeout = getBlockingSendTimeout(); - FutureToSendHandler f2sh = new FutureToSendHandler(); + FutureToSendHandler f2sh = new FutureToSendHandler(wsSession); startMessage(opCode, payload, last, f2sh); try { if (timeout == -1) { @@ -456,7 +456,7 @@ public abstract class WsRemoteEndpointIm } public Future<Void> sendObjectByFuture(Object obj) { - FutureToSendHandler f2sh = new FutureToSendHandler(); + FutureToSendHandler f2sh = new FutureToSendHandler(wsSession); sendObjectByCompletion(obj, f2sh); return f2sh; } Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java?rev=1524694&r1=1524693&r2=1524694&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/WsSession.java Thu Sep 19 11:10:06 2013 @@ -38,6 +38,7 @@ import javax.websocket.Extension; import javax.websocket.MessageHandler; import javax.websocket.PongMessage; import javax.websocket.RemoteEndpoint; +import javax.websocket.SendResult; import javax.websocket.Session; import javax.websocket.WebSocketContainer; @@ -92,6 +93,8 @@ public class WsSession implements Sessio Constants.DEFAULT_BUFFER_SIZE; private volatile long maxIdleTimeout = 0; private volatile long lastActive = System.currentTimeMillis(); + private Map<FutureToSendHandler,FutureToSendHandler> futures = + new ConcurrentHashMap<FutureToSendHandler,FutureToSendHandler>(); /** * Creates a new WebSocket session for communication between the two @@ -415,6 +418,12 @@ public class WsSession implements Sessio state = State.CLOSED; } + + IOException ioe = new IOException(sm.getString("wsSession.messageFailed")); + SendResult sr = new SendResult(ioe); + for (FutureToSendHandler f2sh : futures.keySet()) { + f2sh.onResult(sr); + } } @@ -510,6 +519,25 @@ public class WsSession implements Sessio } } + + /** + * Make the session aware of a {@link FutureToSendHandler} that will need to + * be forcibly closed if the session closes before the + * {@link FutureToSendHandler} completes. + */ + protected void registerFuture(FutureToSendHandler f2sh) { + futures.put(f2sh, f2sh); + } + + + /** + * Remove a {@link FutureToSendHandler} from the set of tracked instances. + */ + protected void unregisterFuture(FutureToSendHandler f2sh) { + futures.remove(f2sh); + } + + @Override public URI getRequestURI() { checkState(); 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=1524694&r1=1524693&r2=1524694&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 Thu Sep 19 11:10:06 2013 @@ -55,6 +55,8 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpointConfig; +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; import javax.websocket.DeploymentException; import javax.websocket.Endpoint; import javax.websocket.Extension; @@ -769,6 +771,27 @@ public class WsWebSocketContainer this.defaultAsyncTimeout = timeout; } + + /** + * Cleans up the resources still in use by WebSocket sessions created from + * this container. This includes closing sessions and cancelling + * {@link Future}s associated with blocking read/writes. + */ + public void destroy() { + CloseReason cr = new CloseReason( + CloseCodes.GOING_AWAY, sm.getString("wsWebSocketContainer.shutdown")); + + for (WsSession session : sessions.keySet()) { + try { + session.close(cr); + } catch (IOException ioe) { + log.debug(sm.getString( + "wsWebSocketContainer.sessionCloseFail", session.getId()), ioe); + } + } + } + + // ----------------------------------------------- BackgroundProcess methods @Override Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsContextListener.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsContextListener.java?rev=1524694&r1=1524693&r2=1524694&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsContextListener.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsContextListener.java Thu Sep 19 11:10:06 2013 @@ -16,25 +16,36 @@ */ package org.apache.tomcat.websocket.server; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** - * In normal usage, this {@link ServletContextListener} is not required as the - * {@link WsSci} performs all the necessary bootstrap. If the {@link WsSci} is - * disabled, this listener must be added manually to every - * {@link javax.servlet.ServletContext} that uses WebSocket to bootstrap the + * In normal usage, this {@link ServletContextListener} does not need to be + * explicitly configured as the {@link WsSci} performs all the necessary + * bootstrap and installs this listener in the {@link ServletContext}. If the + * {@link WsSci} is disabled, this listener must be added manually to every + * {@link ServletContext} that uses WebSocket to bootstrap the * {@link WsServerContainer} correctly. */ public class WsContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { - WsSci.init(sce.getServletContext()); + ServletContext sc = sce.getServletContext(); + // Don't trigger WebSocket initialization if a WebSocket Server + // Container is already present + if (sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE) == null) { + WsSci.init(sce.getServletContext(), false); + } } @Override public void contextDestroyed(ServletContextEvent sce) { - // NOOP + ServletContext sc = sce.getServletContext(); + Object obj = sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE); + if (obj instanceof WsServerContainer) { + ((WsServerContainer) obj).destroy(); + } } } Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsSci.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsSci.java?rev=1524694&r1=1524693&r2=1524694&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsSci.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/websocket/server/WsSci.java Thu Sep 19 11:10:06 2013 @@ -65,7 +65,7 @@ public class WsSci implements ServletCon return; } - WsServerContainer sc = init(ctx); + WsServerContainer sc = init(ctx, true); if (clazzes == null || clazzes.size() == 0) { return; @@ -148,7 +148,8 @@ public class WsSci implements ServletCon } - static WsServerContainer init(ServletContext servletContext) { + static WsServerContainer init(ServletContext servletContext, + boolean initBySciMechanism) { WsServerContainer sc = new WsServerContainer(servletContext); @@ -156,6 +157,11 @@ public class WsSci implements ServletCon Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE, sc); servletContext.addListener(new WsSessionListener(sc)); + // Can't register the ContextListener again if the ContextListener is + // calling this method + if (initBySciMechanism) { + servletContext.addListener(new WsContextListener()); + } return sc; } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org