Blame SOURCES/patch.rhbz1602060

9a9096
--- java/org/apache/tomcat/websocket/WsSession.java.org	2017-03-09 14:51:41.000000000 +0100
9a9096
+++ java/org/apache/tomcat/websocket/WsSession.java	2018-07-18 17:37:46.853657200 +0200
9a9096
@@ -595,8 +595,8 @@
9a9096
             localEndpoint.onError(this, e);
9a9096
         }
9a9096
     }
9a9096
-    
9a9096
-    
9a9096
+
9a9096
+
9a9096
     /**
9a9096
      * Use protected so unit tests can access this method directly.
9a9096
      */
9a9096
@@ -635,29 +635,48 @@
9a9096
      * {@link FutureToSendHandler} completes.
9a9096
      */
9a9096
     protected void registerFuture(FutureToSendHandler f2sh) {
9a9096
-        boolean fail = false;
9a9096
-        synchronized (stateLock) {
9a9096
-            // If the session has already been closed the any registered futures
9a9096
-            // will have been processed so the failure result for this future
9a9096
-            // needs to be set here.
9a9096
-            if (state == State.OPEN || f2sh.isCloseMessage()) {
9a9096
-                // WebSocket session is open or this is the close message
9a9096
-                futures.put(f2sh, f2sh);
9a9096
-            } else if (f2sh.isDone()) {
9a9096
-                // NO-OP. The future completed before the session closed so no
9a9096
-                // need to register in case the session closes before it
9a9096
-                // completes.
9a9096
-            } else {
9a9096
-                // Construct the exception outside of the sync block
9a9096
-                fail = true;
9a9096
-            }
9a9096
+        // Ideally, this code should sync on stateLock so that the correct
9a9096
+        // action is taken based on the current state of the connection.
9a9096
+        // However, a sync on stateLock can't be used here as it will create the
9a9096
+        // possibility of a dead-lock. See BZ 61183.
9a9096
+        // Therefore, a slightly less efficient approach is used.
9a9096
+
9a9096
+        // Always register the future.
9a9096
+        futures.put(f2sh, f2sh);
9a9096
+
9a9096
+        if (state == State.OPEN || f2sh.isCloseMessage()) {
9a9096
+            // The session is open. The future has been registered with the open
9a9096
+            // session. Normal processing continues.
9a9096
+            return;
9a9096
         }
9a9096
 
9a9096
-        if (fail) {
9a9096
-            IOException ioe = new IOException(sm.getString("wsSession.messageFailed"));
9a9096
-            SendResult sr = new SendResult(ioe);
9a9096
-            f2sh.onResult(sr);
9a9096
+        // The session is closed. The future may or may not have been registered
9a9096
+        // in time for it to be processed during session closure.
9a9096
+
9a9096
+        if (f2sh.isDone()) {
9a9096
+            // The future has completed. It is not known if the future was
9a9096
+            // completed normally by the I/O layer or in error by doClose(). It
9a9096
+            // doesn't matter which. There is nothing more to do here.
9a9096
+            return;
9a9096
         }
9a9096
+
9a9096
+        // The session is closed. The Future had not completed when last checked.
9a9096
+        // There is a small timing window that means the Future may have been
9a9096
+        // completed since the last check. There is also the possibility that
9a9096
+        // the Future was not registered in time to be cleaned up during session
9a9096
+        // close.
9a9096
+        // Attempt to complete the Future with an error result as this ensures
9a9096
+        // that the Future completes and any client code waiting on it does not
9a9096
+        // hang. It is slightly inefficient since the Future may have been
9a9096
+        // completed in another thread or another thread may be about to
9a9096
+        // complete the Future but knowing if this is the case requires the sync
9a9096
+        // on stateLock (see above).
9a9096
+        // Note: If multiple attempts are made to complete the Future, the
9a9096
+        //       second and subsequent attempts are ignored.
9a9096
+
9a9096
+        IOException ioe = new IOException(sm.getString("wsSession.messageFailed"));
9a9096
+        SendResult sr = new SendResult(ioe);
9a9096
+        f2sh.onResult(sr);
9a9096
     }
9a9096
 
9a9096