Author: markt Date: Fri Mar 15 20:40:53 2013 New Revision: 1457104 URL: http://svn.apache.org/r1457104 Log: Correctly handle two stage close process.
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java tomcat/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.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=1457104&r1=1457103&r2=1457104&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Fri Mar 15 20:40:53 2013 @@ -51,6 +51,7 @@ wsSession.duplicateHandlerBinary=A binar wsSession.duplicateHandlerPong=A pong message handler has already been configured wsSession.duplicateHandlerText=A text message handler has already been configured wsSession.expireFailed=Unable to close expired session cleanly +wsSession.sendCloseFail=Failed to send close message to remote endpoint wsSession.invalidHandlerTypePong=A pong message handler must implement MessageHandler.Basic 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}] Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java?rev=1457104&r1=1457103&r2=1457104&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsFrameBase.java Fri Mar 15 20:40:53 2013 @@ -283,7 +283,7 @@ public abstract class WsFrameBase { reason = controlBufferText.toString(); } } - wsSession.close(new CloseReason(Util.getCloseCode(code), reason)); + wsSession.onClose(new CloseReason(Util.getCloseCode(code), reason)); } else if (opCode == Constants.OPCODE_PING) { if (wsSession.isOpen()) { wsSession.getBasicRemote().sendPong(controlBufferBinary); Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java?rev=1457104&r1=1457103&r2=1457104&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java Fri Mar 15 20:40:53 2013 @@ -243,12 +243,8 @@ public abstract class WsRemoteEndpointIm boolean dataMessage) { synchronized (messagePartLock) { - if (closed) { - close(); - } else { - fragmented = nextFragmented; - text = nextText; - } + fragmented = nextFragmented; + text = nextText; if (dataMessage) { dataMessageInProgress = false; Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java?rev=1457104&r1=1457103&r2=1457104&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java Fri Mar 15 20:40:53 2013 @@ -70,8 +70,8 @@ public class WsSession implements Sessio private MessageHandler textMessageHandler = null; private MessageHandler binaryMessageHandler = null; private MessageHandler.Whole<PongMessage> pongMessageHandler = null; - private volatile boolean open = true; - private final Object closeLock = new Object(); + private State state = State.OPEN; + private final Object stateLock = new Object(); private final Map<String,Object> userProperties = new ConcurrentHashMap<>(); private volatile int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; @@ -226,7 +226,7 @@ public class WsSession implements Sessio @Override public boolean isOpen() { - return open; + return state == State.OPEN; } @@ -293,45 +293,84 @@ public class WsSession implements Sessio @Override public void close(CloseReason closeReason) throws IOException { // Double-checked locking. OK because open is volatile - if (!open) { + if (state != State.OPEN) { return; } - synchronized (closeLock) { - if (!open) { + synchronized (stateLock) { + if (state != State.OPEN) { return; } - open = false; + state = State.CLOSING; - // Send the close message - // 125 is maximum size for the payload of a control message - ByteBuffer msg = ByteBuffer.allocate(125); - msg.putShort((short) closeReason.getCloseCode().getCode()); - String reason = closeReason.getReasonPhrase(); - if (reason != null && reason.length() > 0) { - msg.put(reason.getBytes(UTF8)); + sendCloseMessage(closeReason); + } + } + + + /** + * Called when a close message is received. Should only ever happen once. + */ + void onClose(CloseReason closeReason) { + + boolean sendCloseMessage = false; + + synchronized (stateLock) { + if (state == State.OPEN) { + sendCloseMessage = true; } - msg.flip(); - try { - wsRemoteEndpoint.startMessageBlock( - Constants.OPCODE_CLOSE, msg, true); - } finally { - webSocketContainer.unregisterSession( - localEndpoint.getClass(), this); - // Fire the onClose event - Thread t = Thread.currentThread(); - ClassLoader cl = t.getContextClassLoader(); - t.setContextClassLoader(applicationClassLoader); + state = State.CLOSED; + + if (sendCloseMessage) { try { - localEndpoint.onClose(this, closeReason); - } finally { - t.setContextClassLoader(cl); + sendCloseMessage(closeReason); + } catch (IOException ioe) { + log.error(sm.getString("wsSession.sendCloseFail"), ioe); } } + + // Close the socket + wsRemoteEndpoint.close(); } } + private void sendCloseMessage(CloseReason closeReason) throws IOException { + // 125 is maximum size for the payload of a control message + ByteBuffer msg = ByteBuffer.allocate(125); + msg.putShort((short) closeReason.getCloseCode().getCode()); + String reason = closeReason.getReasonPhrase(); + if (reason != null && reason.length() > 0) { + msg.put(reason.getBytes(UTF8)); + } + msg.flip(); + try { + wsRemoteEndpoint.startMessageBlock( + Constants.OPCODE_CLOSE, msg, true); + } catch (IOException ioe) { + // Failed to send close message. Close the socket and let the caller + // deal with the Exception + log.error(sm.getString("wsSession.sendCloseFail"), ioe); + wsRemoteEndpoint.close(); + throw ioe; + } finally { + webSocketContainer.unregisterSession( + localEndpoint.getClass(), this); + + // Fire the onClose event + Thread t = Thread.currentThread(); + ClassLoader cl = t.getContextClassLoader(); + t.setContextClassLoader(applicationClassLoader); + try { + localEndpoint.onClose(this, closeReason); + } finally { + t.setContextClassLoader(cl); + } + } + + } + + @Override public URI getRequestURI() { if (request == null) { @@ -426,4 +465,11 @@ public class WsSession implements Sessio } } } + + + private static enum State { + OPEN, + CLOSING, + CLOSED + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org