Author: markt Date: Thu Sep 5 11:22:00 2013 New Revision: 1520281 URL: http://svn.apache.org/r1520281 Log: Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=55524 Refactor to avoid a deadlock. Move the exception handling that may execute user code outside of the sync block.
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java?rev=1520281&r1=1520280&r2=1520281&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java Thu Sep 5 11:22:00 2013 @@ -41,6 +41,13 @@ public class WsOutbound { StringManager.getManager(Constants.Package); public static final int DEFAULT_BUFFER_SIZE = 8192; + /** + * This state lock is used rather than synchronized methods to allow error + * handling to be managed outside of the synchronization else deadlocks may + * occur such as https://issues.apache.org/bugzilla/show_bug.cgi?id=55524 + */ + private final Object stateLock = new Object(); + private UpgradeOutbound upgradeOutbound; private StreamInbound streamInbound; private ByteBuffer bb; @@ -78,22 +85,34 @@ public class WsOutbound { * @throws IOException If a flush is required and an error occurs writing * the WebSocket frame to the client */ - public synchronized void writeBinaryData(int b) throws IOException { - if (closed) { - throw new IOException(sm.getString("outbound.closed")); - } - - if (bb.position() == bb.capacity()) { - doFlush(false); - } - if (text == null) { - text = Boolean.FALSE; - } else if (text == Boolean.TRUE) { - // Flush the character data - flush(); - text = Boolean.FALSE; + public void writeBinaryData(int b) throws IOException { + try { + synchronized (stateLock) { + if (closed) { + throw new IOException(sm.getString("outbound.closed")); + } + + if (bb.position() == bb.capacity()) { + doFlush(false); + } + if (text == null) { + text = Boolean.FALSE; + } else if (text == Boolean.TRUE) { + // Flush the character data + flush(); + text = Boolean.FALSE; + } + bb.put((byte) (b & 0xFF)); + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - bb.put((byte) (b & 0xFF)); } @@ -108,23 +127,35 @@ public class WsOutbound { * @throws IOException If a flush is required and an error occurs writing * the WebSocket frame to the client */ - public synchronized void writeTextData(char c) throws IOException { - if (closed) { - throw new IOException(sm.getString("outbound.closed")); - } - - if (cb.position() == cb.capacity()) { - doFlush(false); - } - - if (text == null) { - text = Boolean.TRUE; - } else if (text == Boolean.FALSE) { - // Flush the binary data - flush(); - text = Boolean.TRUE; + public void writeTextData(char c) throws IOException { + try { + synchronized (stateLock) { + if (closed) { + throw new IOException(sm.getString("outbound.closed")); + } + + if (cb.position() == cb.capacity()) { + doFlush(false); + } + + if (text == null) { + text = Boolean.TRUE; + } else if (text == Boolean.FALSE) { + // Flush the binary data + flush(); + text = Boolean.TRUE; + } + cb.append(c); + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the Inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - cb.append(c); } @@ -137,19 +168,30 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - public synchronized void writeBinaryMessage(ByteBuffer msgBb) - throws IOException { - - if (closed) { - throw new IOException(sm.getString("outbound.closed")); - } + public void writeBinaryMessage(ByteBuffer msgBb) throws IOException { - if (text != null) { - // Empty the buffer - flush(); + try { + synchronized (stateLock) { + if (closed) { + throw new IOException(sm.getString("outbound.closed")); + } + + if (text != null) { + // Empty the buffer + flush(); + } + text = Boolean.FALSE; + doWriteBytes(msgBb, true); + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the Inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - text = Boolean.FALSE; - doWriteBytes(msgBb, true); } @@ -162,19 +204,30 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - public synchronized void writeTextMessage(CharBuffer msgCb) - throws IOException { + public void writeTextMessage(CharBuffer msgCb) throws IOException { - if (closed) { - throw new IOException(sm.getString("outbound.closed")); - } - - if (text != null) { - // Empty the buffer - flush(); + try { + synchronized (stateLock) { + if (closed) { + throw new IOException(sm.getString("outbound.closed")); + } + + if (text != null) { + // Empty the buffer + flush(); + } + text = Boolean.TRUE; + doWriteText(msgCb, true); + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the Inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - text = Boolean.TRUE; - doWriteText(msgCb, true); } @@ -183,11 +236,23 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - public synchronized void flush() throws IOException { - if (closed) { - throw new IOException(sm.getString("outbound.closed")); + public void flush() throws IOException { + try { + synchronized (stateLock) { + if (closed) { + throw new IOException(sm.getString("outbound.closed")); + } + doFlush(true); + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the Inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - doFlush(true); } @@ -270,39 +335,50 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - public synchronized void close(int status, ByteBuffer data) - throws IOException { + public void close(int status, ByteBuffer data) throws IOException { - if (closed) { - return; - } - - // Send any partial data we have try { - doFlush(false); - } finally { - closed = true; - } - - upgradeOutbound.write(0x88); - if (status == 0) { - upgradeOutbound.write(0); - } else if (data == null || data.position() == data.limit()) { - upgradeOutbound.write(2); - upgradeOutbound.write(status >>> 8); - upgradeOutbound.write(status); - } else { - upgradeOutbound.write(2 + data.limit() - data.position()); - upgradeOutbound.write(status >>> 8); - upgradeOutbound.write(status); - upgradeOutbound.write(data.array(), data.position(), - data.limit() - data.position()); + synchronized (stateLock) { + if (closed) { + return; + } + + // Send any partial data we have + try { + doFlush(false); + } finally { + closed = true; + } + + upgradeOutbound.write(0x88); + if (status == 0) { + upgradeOutbound.write(0); + } else if (data == null || data.position() == data.limit()) { + upgradeOutbound.write(2); + upgradeOutbound.write(status >>> 8); + upgradeOutbound.write(status); + } else { + upgradeOutbound.write(2 + data.limit() - data.position()); + upgradeOutbound.write(status >>> 8); + upgradeOutbound.write(status); + upgradeOutbound.write(data.array(), data.position(), + data.limit() - data.position()); + } + upgradeOutbound.flush(); + + bb = null; + cb = null; + upgradeOutbound = null; + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the Inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - upgradeOutbound.flush(); - - bb = null; - cb = null; - upgradeOutbound = null; } @@ -313,7 +389,7 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - public synchronized void pong(ByteBuffer data) throws IOException { + public void pong(ByteBuffer data) throws IOException { sendControlMessage(data, Constants.OPCODE_PONG); } @@ -324,7 +400,7 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - public synchronized void ping(ByteBuffer data) throws IOException { + public void ping(ByteBuffer data) throws IOException { sendControlMessage(data, Constants.OPCODE_PING); } @@ -336,24 +412,36 @@ public class WsOutbound { * * @throws IOException If an error occurs writing to the client */ - private synchronized void sendControlMessage(ByteBuffer data, byte opcode) throws IOException { + private void sendControlMessage(ByteBuffer data, byte opcode) throws IOException { - if (closed) { - throw new IOException(sm.getString("outbound.closed")); - } - - doFlush(false); - - upgradeOutbound.write(0x80 | opcode); - if (data == null) { - upgradeOutbound.write(0); - } else { - upgradeOutbound.write(data.limit() - data.position()); - upgradeOutbound.write(data.array(), data.position(), - data.limit() - data.position()); + try { + synchronized (stateLock) { + if (closed) { + throw new IOException(sm.getString("outbound.closed")); + } + + doFlush(false); + + upgradeOutbound.write(0x80 | opcode); + if (data == null) { + upgradeOutbound.write(0); + } else { + upgradeOutbound.write(data.limit() - data.position()); + upgradeOutbound.write(data.array(), data.position(), + data.limit() - data.position()); + } + + upgradeOutbound.flush(); + } + } catch (IOException ioe) { + // Any IOException is terminal. Make sure the Inbound side knows + // that something went wrong. + // The exception handling needs to be outside of the sync to avoid + // possible deadlocks (e.g. BZ55524) when triggering the inbound + // close as that will execute user code + streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); + throw ioe; } - - upgradeOutbound.flush(); } @@ -372,60 +460,53 @@ public class WsOutbound { throw new IOException(sm.getString("outbound.closed")); } - try { - // Work out the first byte - int first = 0x00; - if (finalFragment) { - first = first + 0x80; - } - if (firstFrame) { - if (text.booleanValue()) { - first = first + 0x1; - } else { - first = first + 0x2; - } - } - // Continuation frame is OpCode 0 - upgradeOutbound.write(first); - - if (buffer.limit() < 126) { - upgradeOutbound.write(buffer.limit()); - } else if (buffer.limit() < 65536) { - upgradeOutbound.write(126); - upgradeOutbound.write(buffer.limit() >>> 8); - upgradeOutbound.write(buffer.limit() & 0xFF); - } else { - // Will never be more than 2^31-1 - upgradeOutbound.write(127); - upgradeOutbound.write(0); - upgradeOutbound.write(0); - upgradeOutbound.write(0); - upgradeOutbound.write(0); - upgradeOutbound.write(buffer.limit() >>> 24); - upgradeOutbound.write(buffer.limit() >>> 16); - upgradeOutbound.write(buffer.limit() >>> 8); - upgradeOutbound.write(buffer.limit() & 0xFF); - } - - // Write the content - upgradeOutbound.write(buffer.array(), buffer.arrayOffset(), - buffer.limit()); - upgradeOutbound.flush(); - - // Reset - if (finalFragment) { - text = null; - firstFrame = true; + // Work out the first byte + int first = 0x00; + if (finalFragment) { + first = first + 0x80; + } + if (firstFrame) { + if (text.booleanValue()) { + first = first + 0x1; } else { - firstFrame = false; + first = first + 0x2; } - bb.clear(); - } catch (IOException ioe) { - // Any IOException is terminal. Make sure the Inbound side knows - // that something went wrong. - streamInbound.doOnClose(Constants.STATUS_CLOSED_UNEXPECTEDLY); - throw ioe; } + // Continuation frame is OpCode 0 + upgradeOutbound.write(first); + + if (buffer.limit() < 126) { + upgradeOutbound.write(buffer.limit()); + } else if (buffer.limit() < 65536) { + upgradeOutbound.write(126); + upgradeOutbound.write(buffer.limit() >>> 8); + upgradeOutbound.write(buffer.limit() & 0xFF); + } else { + // Will never be more than 2^31-1 + upgradeOutbound.write(127); + upgradeOutbound.write(0); + upgradeOutbound.write(0); + upgradeOutbound.write(0); + upgradeOutbound.write(0); + upgradeOutbound.write(buffer.limit() >>> 24); + upgradeOutbound.write(buffer.limit() >>> 16); + upgradeOutbound.write(buffer.limit() >>> 8); + upgradeOutbound.write(buffer.limit() & 0xFF); + } + + // Write the content + upgradeOutbound.write(buffer.array(), buffer.arrayOffset(), + buffer.limit()); + upgradeOutbound.flush(); + + // Reset + if (finalFragment) { + text = null; + firstFrame = true; + } else { + firstFrame = false; + } + bb.clear(); } 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=1520281&r1=1520280&r2=1520281&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Thu Sep 5 11:22:00 2013 @@ -154,6 +154,11 @@ Correct several incorrect formats of <code>JdkLoggerFormatter</code>. (kfujino) </fix> + <fix> + <bug>55524</bug>: Refactor to avoid a possible deadlock when handling an + <code>IOException</code> during output when using Tomcat' + proprietary (and deprecated) WebSocket API. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org